diff --git a/.gitignore b/.gitignore index a813de4..eb161bb 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,11 @@ target/ */target/ # Jupyter Notebook .ipynb_checkpoints +*/.ipynb_checkpoints/* + +# ossutil +.ossutil_checkpoint +*/.ossutil_checkpoint/* # pyenv .python-version diff --git "a/notebook/LeetCode/\351\235\242\350\257\225\351\242\23064.\346\261\2021+2+\342\200\246+n.ipynb" "b/notebook/LeetCode/\351\235\242\350\257\225\351\242\23064.\346\261\2021+2+\342\200\246+n.ipynb" new file mode 100644 index 0000000..609bf3c --- /dev/null +++ "b/notebook/LeetCode/\351\235\242\350\257\225\351\242\23064.\346\261\2021+2+\342\200\246+n.ipynb" @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# https://leetcode-cn.com/problems/qiu-12n-lcof\n", + "class Solution:\n", + " def __init__(self):\n", + " self.res = 0\n", + " def sumNums(self, n: int) -> int:\n", + " n > 1 and self.sumNums(n - 1)\n", + " self.res += n\n", + " return self.res\n", + "\n", + "s = Solution()\n", + "s.sumNums(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "45" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "s = Solution()\n", + "s.sumNums(9)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class Solution:\n", + " def __init__(self):\n", + " self.res = 0\n", + " def sumNums(self, n: int) -> int:\n", + " n > 1 and self.sumNums(n - 1)\n", + " self.res += n\n", + " return self.res" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "12" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "6 << 1" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "6 >> 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebook/aliyun/oss/sts/signature.ipynb b/notebook/aliyun/oss/sts/signature.ipynb new file mode 100644 index 0000000..d4b5546 --- /dev/null +++ b/notebook/aliyun/oss/sts/signature.ipynb @@ -0,0 +1,160 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 90, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('AccessKeyId', 'testid'),\n", + " ('Action', 'AssumeRole'),\n", + " ('Format', 'JSON'),\n", + " ('RoleArn', 'acs:ram::1234567890123:role/firstrole'),\n", + " ('RoleSessionName', 'client'),\n", + " ('SignatureMethod', 'HMAC-SHA1'),\n", + " ('SignatureNonce', '571f8fb8-506e-11e5-8e12-b8e8563dc8d2'),\n", + " ('SignatureVersion', '1.0'),\n", + " ('Timestamp', '2015-09-01T05:57:34Z'),\n", + " ('Version', '2015-04-01')]" + ] + }, + "execution_count": 90, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Python Version: 3.7\n", + "# 计算签名\n", + "# https://help.aliyun.com/document_detail/28761.html?spm=a2c4g.11186623.6.791.66725328JBIR5G\n", + "\n", + "\n", + "#https://sts.aliyuncs.com/?&AccessKeyId=testid&SignatureMethod=HMAC-SHA1&Version=2015-04-01&Action=AssumeRole&SignatureNonce=571f8fb8-506e-11e5-8e12-b8e8563dc8d2\n", + "params = {\n", + " \"Action\": \"AssumeRole\",\n", + " \"Format\": \"JSON\",\n", + " \"Version\": \"2015-04-01\",\n", + " \"SignatureMethod\": \"HMAC-SHA1\",\n", + " \"SignatureNonce\": \"571f8fb8-506e-11e5-8e12-b8e8563dc8d2\",\n", + " \"SignatureVersion\": \"1.0\",\n", + " \"AccessKeyId\": \"testid\",\n", + " \"Timestamp\": \"2015-09-01T05:57:34Z\",\n", + " \"RoleArn\": \"acs:ram::1234567890123:role/firstrole\",\n", + " \"RoleSessionName\": \"client\"\n", + "}\n", + "\n", + "# 按照首字母排序\n", + "params_arr = list(params.items())\n", + "params_arr.sort(key=lambda x: x[0])\n", + "params_arr\n" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'GET&%2F&AccessKeyId%3Dtestid%26Action%3DAssumeRole%26Format%3DJSON%26RoleArn%3Dacs%253Aram%253A%253A1234567890123%253Arole%252Ffirstrole%26RoleSessionName%3Dclient%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D571f8fb8-506e-11e5-8e12-b8e8563dc8d2%26SignatureVersion%3D1.0%26Timestamp%3D2015-09-01T05%253A57%253A34Z%26Version%3D2015-04-01'" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from collections import OrderedDict\n", + "import urllib.parse\n", + "\n", + "# 拼接 sign_str\n", + "sign_str = \"GET&%2F&\"\n", + "sign_dict = OrderedDict(params_arr)\n", + "params_str = urllib.parse.urlencode(sign_dict)\n", + "params_str = urllib.parse.quote(params_str)\n", + "\n", + "sign_str += params_str\n", + "sign_str" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [], + "source": [ + "# 验证签名用的 str 是否正确\n", + "eg = \"GET&%2F&AccessKeyId%3Dtestid%26Action%3DAssumeRole%26Format%3DJSON%26RoleArn%3Dacs%253Aram%253A%253A1234567890123%253Arole%252Ffirstrole%26RoleSessionName%3Dclient%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D571f8fb8-506e-11e5-8e12-b8e8563dc8d2%26SignatureVersion%3D1.0%26Timestamp%3D2015-09-01T05%253A57%253A34Z%26Version%3D2015-04-01\"\n", + "assert sign_str == eg" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'gNI7b0AyKZHxDgjBGPDgJ1Ce3L4='" + ] + }, + "execution_count": 82, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import hashlib\n", + "import hmac\n", + "import base64\n", + "h = hmac.new('testsecret&'.encode('utf-8'), sign_str.encode('utf-8'), hashlib.sha1)\n", + "sign = h.digest()\n", + "sign = base64.b64encode(sign).decode()\n", + "sign" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": {}, + "outputs": [], + "source": [ + "# 验证签名值\n", + "assert sign == 'gNI7b0AyKZHxDgjBGPDgJ1Ce3L4='" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebook/config.py b/notebook/config.py new file mode 100644 index 0000000..b47accf --- /dev/null +++ b/notebook/config.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +# Author: wxnacy(wxnacy@gmail.com) +# Description: + +DATABASE_URL = 'mysql://root:wxnacy@127.0.0.1:3306/study?charset=utf8mb4' \ No newline at end of file diff --git a/notebook/convert.sh b/notebook/convert.sh new file mode 100755 index 0000000..ff06820 --- /dev/null +++ b/notebook/convert.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Author: wxnacy(wxnacy@gmail.com) +# Description: + +ipynb_files=`find . -name "*.ipynb" | grep -v checkpoints` +for file in $ipynb_files +do + outdir=`python -c "import os;print(os.path.dirname('${file}'.replace('./', './static/')))"` + jupyter nbconvert $file --output-dir $outdir +done diff --git a/notebook/create_index.py b/notebook/create_index.py new file mode 100644 index 0000000..3e33aa7 --- /dev/null +++ b/notebook/create_index.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +# Author: wxnacy(wxnacy@gmail.com) +# Description: + +HTML = """\ + + +
+ +# https://leetcode-cn.com/problems/qiu-12n-lcof
+class Solution:
+ def __init__(self):
+ self.res = 0
+ def sumNums(self, n: int) -> int:
+ n > 1 and self.sumNums(n - 1)
+ self.res += n
+ return self.res
+
+s = Solution()
+s.sumNums(3)
+
s = Solution()
+s.sumNums(9)
+
class Solution:
+ def __init__(self):
+ self.res = 0
+ def sumNums(self, n: int) -> int:
+ n > 1 and self.sumNums(n - 1)
+ self.res += n
+ return self.res
+
6 << 1
+
6 >> 1
+
+
# Python Version: 3.7
+# 计算签名
+# https://help.aliyun.com/document_detail/28761.html?spm=a2c4g.11186623.6.791.66725328JBIR5G
+
+
+#https://sts.aliyuncs.com/?&AccessKeyId=testid&SignatureMethod=HMAC-SHA1&Version=2015-04-01&Action=AssumeRole&SignatureNonce=571f8fb8-506e-11e5-8e12-b8e8563dc8d2
+params = {
+ "Action": "AssumeRole",
+ "Format": "JSON",
+ "Version": "2015-04-01",
+ "SignatureMethod": "HMAC-SHA1",
+ "SignatureNonce": "571f8fb8-506e-11e5-8e12-b8e8563dc8d2",
+ "SignatureVersion": "1.0",
+ "AccessKeyId": "testid",
+ "Timestamp": "2015-09-01T05:57:34Z",
+ "RoleArn": "acs:ram::1234567890123:role/firstrole",
+ "RoleSessionName": "client"
+}
+
+# 按照首字母排序
+params_arr = list(params.items())
+params_arr.sort(key=lambda x: x[0])
+params_arr
+
from collections import OrderedDict
+import urllib.parse
+
+# 拼接 sign_str
+sign_str = "GET&%2F&"
+sign_dict = OrderedDict(params_arr)
+params_str = urllib.parse.urlencode(sign_dict)
+params_str = urllib.parse.quote(params_str)
+
+sign_str += params_str
+sign_str
+
# 验证签名用的 str 是否正确
+eg = "GET&%2F&AccessKeyId%3Dtestid%26Action%3DAssumeRole%26Format%3DJSON%26RoleArn%3Dacs%253Aram%253A%253A1234567890123%253Arole%252Ffirstrole%26RoleSessionName%3Dclient%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D571f8fb8-506e-11e5-8e12-b8e8563dc8d2%26SignatureVersion%3D1.0%26Timestamp%3D2015-09-01T05%253A57%253A34Z%26Version%3D2015-04-01"
+assert sign_str == eg
+
import hashlib
+import hmac
+import base64
+h = hmac.new('testsecret&'.encode('utf-8'), sign_str.encode('utf-8'), hashlib.sha1)
+sign = h.digest()
+sign = base64.b64encode(sign).decode()
+sign
+
# 验证签名值
+assert sign == 'gNI7b0AyKZHxDgjBGPDgJ1Ce3L4='
+
+
# 您可以通过将数据库用作异步上下文管理器来控制数据库的连接/断开连接。
+import config
+from databases import Database
+async with Database(config.DATABASE_URL) as database:
+ row = await database.fetch_one(query="select * from test_user;")
+ print(row)
+
'''
+If you're integrating against a web framework,
+then you'll probably want to hook into framework
+startup or shutdown events. For example, with Starlette you would use the following:
+'''
+@app.on_event("startup")
+async def startup():
+ await database.connect()
+
+@app.on_event("shutdown")
+async def shutdown():
+ await database.disconnect()
+
# 连接选项
+# Use an SSL connection.
+database = Database('postgresql://localhost/example?ssl=true')
+
# Use a connection pool of between 5-20 connections.
+database = Database('mysql://localhost/example?min_size=5&max_size=20')
+
# 使用键值对设置选项
+database = Database('postgresql://localhost/example', ssl=True, min_size=5, max_size=20)
+
# 测试连接池
+
+from databases import Database
+import multiprocessing as mp
+import config
+
+database = Database(config.DATABASE_URL, min_size=2, max_size=5)
+await database.connect()
+
+def sleep():
+ print("hw")
+ # res = database.fetch_one("select sleep(1)")
+ print(res)
+
+pool = mp.Pool(10)
+for i in range(10):
+ pool.apply_async(sleep, ())
+await database.disconnect()
+
await database.disconnect()
+
+
'''
+https://www.encode.io/databases/
+databases 为您提供了对一系列数据库的简单异步支持。
+
+它使您可以使用功能强大的SQLAlchemy Core表达式语言进行查询,并提供对PostgreSQL,MySQL和SQLite的支持。
+
+'''
+
+# 安装
+!pip install databases[mysql]
+
# Create a database instance, and connect to it.
+from databases import Database
+import config
+database = Database(config.DATABASE_URL)
+await database.connect()
+
# Create a table.
+query = """
+DROP TABLE IF EXISTS `test_user`;
+CREATE TABLE `test_user` (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), score INT);
+"""
+await database.execute(query=query)
+
# Insert some data.
+query = "INSERT INTO test_user (name, score) VALUES (:name, :score)"
+values = [
+ {"name": "Daisy", "score": 92},
+ {"name": "Neil", "score": 87},
+ {"name": "Carol", "score": 43},
+]
+await database.execute_many(query=query, values=values)
+
# Run a database query.
+query = "SELECT * FROM test_user"
+await database.fetch_all(query=query)
+
# Fetch single row
+query = "SELECT * FROM test_user WHERE id = :id"
+await database.fetch_one(query=query, values={"id": 1})
+
# Close all connection in the connection pool
+await database.disconnect()
+
+
# 使用 sqlalchemy 进行数据库操作
+import sqlalchemy
+from sqlalchemy import Column
+from sqlalchemy import Integer
+from sqlalchemy import String
+from sqlalchemy import Boolean
+import config
+
+
+metadata = sqlalchemy.MetaData()
+
+user = sqlalchemy.Table(
+ "test_user",
+ metadata,
+ Column("id", Integer, primary_key=True),
+ Column("name", String(length=100)),
+ Column("score", Integer),
+)
+
# Create a database instance, and connect to it.
+from databases import Database
+database = Database(config.DATABASE_URL)
+await database.connect()
+
# Execute
+query = user.insert()
+values = {"name": "wxnacy", "score": 100}
+await database.execute(query=query, values=values)
+
# Execute many
+query = user.insert()
+values = [
+ {"name": "wxnacy", "score": 100},
+ {"name": "wen", "score": 100}
+]
+await database.execute_many(query=query, values=values)
+
# Fetch multiple rows
+query = user.select()
+rows = await database.fetch_all(query=query)
+rows, type(rows)
+
# Fetch single row
+query = user.select()
+row = await database.fetch_one(query=query)
+row, type(row), row.id
+
# Fetch single value, defaults to `column=0`.
+query = user.select()
+await database.fetch_val(query=query)
+
# Fetch multiple rows without loading them all into memory at once
+query = user.select()
+async for row in database.iterate(query=query):
+ print(row)
+
help(user.select)
+
from sqlalchemy import text
+query = user.select(text("id = 1"))
+await database.fetch_all(query=query)
+
+
# 生成名字
+!faker name
+
# 设置语言
+!faker -l zh_CN name
+
# 重复三次,并设置后缀
+!faker -r 3 -s ";" name
+
# 帮助文档
+!faker -h
+
from faker import Faker
+
+# 设置中文
+fake = Faker('zh_CN')
+fake.name()
+
# 设置多个语言
+fake = Faker(['zh_CN', 'ja_JP', 'en_US'])
+for _ in range(10):
+ print(fake.name())
+
# 设置多语言的权重
+
+from collections import OrderedDict
+locales = OrderedDict([
+ ('en-US', 1),
+ ('en-PH', 2),
+ ('ja_JP', 3),
+])
+fake = Faker(locales)
+
# 获取指定的语言列表
+fake.locales
+
# 单独获取列表中某一个语言
+fake['en-US'].name()
+
+
#!/usr/bin/env python
+# -*- coding:utf-8 -*-
+# Author: wxnacy(wxnacy@gmail.com)
+# Faker 快速开始
+# https://github.com/joke2k/faker
+
+from faker import Faker
+fake = Faker()
+# 生成名字
+fake.name()
+
# 生成地址
+fake.address()
+
# 生成文本
+fake.text()
+
# 生成 user_agent
+fake.user_agent()
+
# 生成 ipv4
+fake.ipv4()
+
# 生成 android 平台名称
+fake.android_platform_token()
+
# 生成 ios 平台名称
+fake.ios_platform_token()
+
# 查看 faker 属性
+dir(fake)
+
+
!pip show databases
+
+
import hashlib
+
+# 计算字符串 md5 值
+message = "wxnacy"
+md5 = hashlib.md5()
+md5.update(message.encode('utf-8'))
+md5.hexdigest()
+
# 计算字符串 md5 值
+hashlib.md5(message.encode('utf-8')).hexdigest()
+
# 计算文件 md5 值
+def encrypt_file(filename):
+ hash_md5 = hashlib.md5()
+ with open(filename, "rb") as f:
+ for chunk in iter(lambda: f.read(4096), b""):
+ hash_md5.update(chunk)
+ return hash_md5.hexdigest()
+
+encrypt_file('./hashlib.ipynb')
+
# 计算网址内容的 md5 值
+from urllib.request import urlopen
+
+def encrypt_url(url, max_file_size=100 * 1024 * 1024):
+ remote = urlopen(url)
+ h = hashlib.md5()
+
+ total_read = 0
+ while True:
+ data = remote.read(4096)
+ total_read += 4096
+
+ if not data or total_read > max_file_size:
+ break
+ h.update(data)
+ return h.hexdigest()
+
+encrypt_url("https://wxnacy.com")
+
# 查看其它 hash 值算法
+dir(hashlib)
+
+
# 计算 hmac_sha1 加密值
+import hmac
+import hashlib
+
+message = 'wxnacy'
+key = 'wxnacy.com'
+
+h = hmac.new(key.encode('utf-8'), message.encode('utf-8'), hashlib.sha1)
+# 计算 16 进制编码字符串
+h.hexdigest()
+
# 使用 base64 将 hmac 结果编码为字符串
+import base64
+base64.b64encode(h.digest()).decode()
+
+
!pip install mysqlclient
+
# pip install sqlalchemy pymysql
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+import config
+
+SQLALCHEMY_DATABASE_URL = config.DATABASE_URL
+
+engine = create_engine(
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
+)
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+Base = declarative_base()
+
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
+
+class User(Base):
+ __tablename__ = 'test_user'
+ id = Column(Integer, primary_key = True)
+ name = Column(String, default="")
+ score = Column(Integer, default=0)
+
import sqlalchemy
+for module in sqlalchemy, sqlalchemy.orm:
+ print(module.__all__)
+ for key in module.__all__:
+ print(key)
+
+
# https://www.urlencoder.io/python/
+import urllib
+urllib.parse.quote('w xnacy')
+
urllib.parse.quote_plus('w xnacy')
+
urllib.parse.quote('/')
+
urllib.parse.quote('/', safe='')
+
params = dict(text = 'name is wxnacy', url='https://wxnacy.com')
+urllib.parse.urlencode(params)
+
urllib.parse.urlencode(params, quote_via=urllib.parse.quote)
+
params = {'name': 'Rajeev Singh', 'phone': ['+919999999999', '+628888888888']}
+urllib.parse.urlencode(params, doseq=True)
+
+
# Python Version: 3.7
+# 计算签名
+# https://help.aliyun.com/document_detail/28761.html?spm=a2c4g.11186623.6.791.66725328JBIR5G
+
+
+#https://sts.aliyuncs.com/?&AccessKeyId=testid&SignatureMethod=HMAC-SHA1&Version=2015-04-01&Action=AssumeRole&SignatureNonce=571f8fb8-506e-11e5-8e12-b8e8563dc8d2
+params = {
+ "Action": "AssumeRole",
+ "Format": "JSON",
+ "Version": "2015-04-01",
+ "SignatureMethod": "HMAC-SHA1",
+ "SignatureNonce": "571f8fb8-506e-11e5-8e12-b8e8563dc8d2",
+ "SignatureVersion": "1.0",
+ "AccessKeyId": "testid",
+ "Timestamp": "2015-09-01T05:57:34Z",
+ "RoleArn": "acs:ram::1234567890123:role/firstrole",
+ "RoleSessionName": "client"
+}
+
+# 按照首字母排序
+params_arr = list(params.items())
+params_arr.sort(key=lambda x: x[0])
+params_arr
+
from collections import OrderedDict
+import urllib.parse
+
+# 拼接 sign_str
+sign_str = "GET&%2F&"
+sign_dict = OrderedDict(params_arr)
+params_str = urllib.parse.urlencode(sign_dict)
+params_str = urllib.parse.quote(params_str)
+
+sign_str += params_str
+sign_str
+
# 验证签名用的 str 是否正确
+eg = "GET&%2F&AccessKeyId%3Dtestid%26Action%3DAssumeRole%26Format%3DJSON%26RoleArn%3Dacs%253Aram%253A%253A1234567890123%253Arole%252Ffirstrole%26RoleSessionName%3Dclient%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D571f8fb8-506e-11e5-8e12-b8e8563dc8d2%26SignatureVersion%3D1.0%26Timestamp%3D2015-09-01T05%253A57%253A34Z%26Version%3D2015-04-01"
+assert sign_str == eg
+
import hashlib
+import hmac
+import base64
+h = hmac.new('testsecret&'.encode('utf-8'), sign_str.encode('utf-8'), hashlib.sha1)
+sign = h.digest()
+sign = base64.b64encode(sign).decode()
+sign
+
# 验证签名值
+assert sign == 'gNI7b0AyKZHxDgjBGPDgJ1Ce3L4='
+
+
!echo $PYTHONPATH
+
import config
+config.DATABASE_URL
+
import os;os.path.dirname('./test.html'.replace('./', './static/'))
+
'./test.html'.replace('./', './static')
+
+