bugfix
This commit is contained in:
parent
a8104356b7
commit
7ef6a33056
47
README.md
Normal file → Executable file
47
README.md
Normal file → Executable file
@ -1,2 +1,49 @@
|
|||||||
# gadget
|
# gadget
|
||||||
|
a light wight web server base on aiohttp
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
* 中文文档请看[这里](docs/cn/index.md)
|
||||||
|
* English documents is under writing
|
||||||
|
|
||||||
|
## Dependent
|
||||||
|
* [ahserver](https://github.com/yumoqing/ahserver)
|
||||||
|
* [sqlor](https://github.com/yumoqing/sqlor) if you want to use database
|
||||||
|
* [apppublic](https://github.com/yumoqing/apppublic)
|
||||||
|
|
||||||
|
## Download
|
||||||
|
```
|
||||||
|
git clone git@github.com:yumoqing/gadget.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
please look [ahserver](https://github.com/yumoqing/ahserver) to learn how to configure
|
||||||
|
|
||||||
|
### support https
|
||||||
|
under "website" in the conf/config.json file, identify ssl with "crtfile" and "keyfile"
|
||||||
|
like this.
|
||||||
|
```
|
||||||
|
"website":{
|
||||||
|
"ssl":{
|
||||||
|
"crtfile":"$[workdir]$/conf/www.bsppo.com.pem",
|
||||||
|
"keyfile":"$[workdir]$/conf/www.bsppo.com.key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### log configure
|
||||||
|
In the conf/config.json, need to config log, you need to identify "name", "levelname" and "logfile"
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
"logger":{
|
||||||
|
"name":"gadget",
|
||||||
|
"levelname":"debug",
|
||||||
|
"logfile":"$[workdir]$/logs/gadget.log"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## Test
|
||||||
|
the test folder contains everything need for a base test.
|
||||||
|
|
||||||
|
please to go test folder and run
|
||||||
|
```
|
||||||
|
python ../src/gadget.py
|
||||||
|
```
|
||||||
|
65
docs/cn/README.md
Executable file
65
docs/cn/README.md
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
# gadget
|
||||||
|
一个python开发的基于aiohttp的异步应用服务器,集成了1)用户认证与权健,2)数据库操作,3)后台开发必须的包和函数
|
||||||
|
|
||||||
|
gadget支持多进程,或多主机部署(需安装redis),在nginx后面提供负载均衡,可按照业务需求动态扩展,提升系统处理能力。
|
||||||
|
|
||||||
|
功能拓展能力,提供两种扩展方法,gadget源码级扩展,或在工作目录的plugins目录下部署扩展的源码
|
||||||
|
|
||||||
|
gadget运行需要指定一个工作目录,在工作目录中需有以下文件或目录:
|
||||||
|
* conf目录 - 配置文件目录
|
||||||
|
* logs目录 - 日志文件目录
|
||||||
|
* plugins目录 - 扩展目录
|
||||||
|
* i18n目录 - 国际化支持
|
||||||
|
* files目录(可在conf/config.json文件中指定其他位置)
|
||||||
|
* wwwroot目录(可在conf/config.json文件中指定其他位置)
|
||||||
|
|
||||||
|
## 运行环境和依赖
|
||||||
|
* 操作系统支持Windows, MacOS, linux
|
||||||
|
* 数据库支持:sqlite3,mysql, mariadb, postgresql,sqlserver, oracle, teradata,华为的guass等(源码方式运行时需提供相应数据库的驱动包)
|
||||||
|
* python 3.7以上版本
|
||||||
|
* redis 当使用redis_storage时需要
|
||||||
|
* [apppublic](https://github.com/yumoqing/appPublic)
|
||||||
|
* [sqlor](https://github.com/yumoqing/sqlor)
|
||||||
|
* [ahserver](https://github.com/yumoqing/ahserver)
|
||||||
|
* [dataui](https://github.com/yumoqing/dataui)
|
||||||
|
* 以及apppublic, sqlor, ahserver和dataui包所依赖的包或模块
|
||||||
|
|
||||||
|
## 运行gadget
|
||||||
|
运行gadget的步骤
|
||||||
|
1 转到工作目录
|
||||||
|
2 gadget [ -p port ]
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
gadget的配置信息需放在工作目录的conf/config.json文件中,有关配置相关内容请看[config.json](config.md)
|
||||||
|
## 模版
|
||||||
|
gadget支持jinja2模版,模版中可以使用gadget定义的所有模块,变量和函数
|
||||||
|
|
||||||
|
## 数据库操作
|
||||||
|
gadget为每个请求提供数据库操作, gadget不提供跨请求的数据库操作,请求中的数据库操作要么整体成功,要么整体失败,gadget使用[sqlor](https://github/yumoqing/sqlor)提供数据库支持。 请看[数据库操作说明](sql.md)
|
||||||
|
|
||||||
|
## 脚本
|
||||||
|
gadget支持在后台使用受限的python脚本,后缀为“.dspy”
|
||||||
|
|
||||||
|
## 用户认证和授权
|
||||||
|
gadget在缺少状态下不做用户认证和权健,要提供此功能需在plugins目录下放一个如下形式的脚本
|
||||||
|
```
|
||||||
|
# auth.py
|
||||||
|
from ahserver.auth_api import AuthAPI
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
async def checkUserPermission(self, user, path):
|
||||||
|
“”“
|
||||||
|
user:用户id,None代表没有登录
|
||||||
|
path:访问目录, path等于URL(“http(s)://x.com/ll/ll”) 中的“/ll/ll"部分
|
||||||
|
返回值:True,有权限
|
||||||
|
False,无权限
|
||||||
|
如果返回False并且user为None,返回前端401,否则403
|
||||||
|
”“”
|
||||||
|
self.info(f'checkUserPermission():{user} access to {path} ..')
|
||||||
|
# 这里做实际的判断,看看user是否有访问path的权限
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
AuthAPI.checkUserPermission = checkUserPermission
|
||||||
|
```
|
||||||
|
|
206
docs/cn/config.md
Executable file
206
docs/cn/config.md
Executable file
@ -0,0 +1,206 @@
|
|||||||
|
#
|
||||||
|
|
||||||
|
## 宏
|
||||||
|
使用$[x]$来引用宏,目前config.json文件中支持两个宏
|
||||||
|
### workdir
|
||||||
|
工作目录,gadget程序启动时的目录
|
||||||
|
### ProgramPath
|
||||||
|
gadget程序所在目录
|
||||||
|
|
||||||
|
例子
|
||||||
|
```
|
||||||
|
"logger":{
|
||||||
|
"name":"iptvserver",
|
||||||
|
"levelname":"debug",
|
||||||
|
"logfile":"$[workdir]$/logs/iptv.log"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
上述例子定义了logfile为工作目录下的logs/iptv.log
|
||||||
|
|
||||||
|
## logger
|
||||||
|
定义日志
|
||||||
|
|
||||||
|
例子
|
||||||
|
```
|
||||||
|
"logger":{
|
||||||
|
"name":"iptvserver",
|
||||||
|
"levelname":"debug",
|
||||||
|
"logfile":"$[workdir]$/logs/iptv.log"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
### name
|
||||||
|
日志名称
|
||||||
|
### levelname
|
||||||
|
日志级别名称
|
||||||
|
* debug
|
||||||
|
* info
|
||||||
|
* error
|
||||||
|
### logfile
|
||||||
|
日志文件路径
|
||||||
|
|
||||||
|
## databases
|
||||||
|
字典类型,每个key定义一个数据库定义, 支持链接多个数据库,可以同时定义多个不同类型的数据库
|
||||||
|
|
||||||
|
例子
|
||||||
|
```
|
||||||
|
“databases”:{
|
||||||
|
"kboss":{
|
||||||
|
"driver":"aiomysql",
|
||||||
|
"async_mode":true,
|
||||||
|
"coding":"utf8",
|
||||||
|
"dbname":"kboss",
|
||||||
|
"maxconn":100,
|
||||||
|
"kwargs":{
|
||||||
|
"user":"test",
|
||||||
|
"db":"kboss",
|
||||||
|
"password":"QUZVcXg5V1p1STMybG5Ia6mX9D0v7+g=",
|
||||||
|
"host":"localhost"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
上述定义了一个名为“kboss”的数据库,使用aiomysql“模块操作数据库
|
||||||
|
异步模式
|
||||||
|
最大支持100个链接,链接参数等
|
||||||
|
|
||||||
|
## website
|
||||||
|
字典类型,定义website属性
|
||||||
|
### port
|
||||||
|
端口号
|
||||||
|
|
||||||
|
### host
|
||||||
|
定义网站主机ip地址,通常设置为”0.0.0.0“, 内网外网都能访问
|
||||||
|
|
||||||
|
### ssl
|
||||||
|
定义网站使用https访问,格式如下
|
||||||
|
```
|
||||||
|
"ssl":{
|
||||||
|
"crtfile":证书绝对路径,
|
||||||
|
"keyfile":证书密码文件绝对路径
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### coding
|
||||||
|
定义网站编码,建议使用”utf-8“编码
|
||||||
|
|
||||||
|
### processors
|
||||||
|
数组,定义文件后缀网站路由,每一个元素定义一个后缀
|
||||||
|
|
||||||
|
例子
|
||||||
|
```
|
||||||
|
"processors":[
|
||||||
|
[".proxy","proxy"],
|
||||||
|
[".xlsxds","xlsxds"],
|
||||||
|
[".sqlds","sqlds"],
|
||||||
|
[".tmpl.js","tmpl"],
|
||||||
|
[".tmpl.css","tmpl"],
|
||||||
|
[".tmpl.html","tmpl"],
|
||||||
|
[".bcrud", "bricks_crud"],
|
||||||
|
[".tmpl","tmpl"],
|
||||||
|
[".ui","tmpl"],
|
||||||
|
[".dspy","dspy"],
|
||||||
|
[".md","md"]
|
||||||
|
|
||||||
|
```
|
||||||
|
上述例子定义:
|
||||||
|
* .proxy 后缀的路径,使用ProxyProcessor来处理
|
||||||
|
* .xlsxds后缀的路径,使用XlsxdsProcessor处理器来处理
|
||||||
|
* .sqlds后缀的路径,采用SqldsProcessor处理器来处理
|
||||||
|
* .tmpl.js后缀的路径,使用TmplProcessor处理器来处理
|
||||||
|
* .tmpl.css后缀的路径,使用TmplProcessor处理器来处理
|
||||||
|
* .tmpl.html后缀的路径,使用TmplProcessor处理器来处理
|
||||||
|
* .bcrud后缀的路径,使用BricksCrudProcessor处理器来处理
|
||||||
|
* .tmpl后缀的路径,使用TmplProcessor处理器来处理
|
||||||
|
* .ui后缀的路径,使用TmplProcessor处理器来处理
|
||||||
|
* .dspy后缀的路径,使用DspyProcessor处理器来处理
|
||||||
|
* .md后缀的路径,使用MarkdownProcessor处理器来处理
|
||||||
|
|
||||||
|
### startswiths
|
||||||
|
数组类型,定义网站的前缀路由,前缀路由使用注册函数实现
|
||||||
|
|
||||||
|
例子
|
||||||
|
```
|
||||||
|
"startswiths":[
|
||||||
|
{
|
||||||
|
"leading":"/public/publickey",
|
||||||
|
"registerfunction":"getPublicKey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"leading":"/public/i18n",
|
||||||
|
"registerfunction":"getI18nMapping"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"leading":"/idfile",
|
||||||
|
"registerfunction":"idFileDownload"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"leading":"/thumb",
|
||||||
|
"registerfunction":"makeThumb",
|
||||||
|
"width":512,
|
||||||
|
"quality":50,
|
||||||
|
"keep_ratio":1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
上述定义了4个前缀路由,除“leading”,”registerfunction“外的属性均被当作参数传递给注册函数
|
||||||
|
|
||||||
|
### rsakey
|
||||||
|
支持rsa公开密钥加密,在这个属性下提供rsa的私钥和公钥
|
||||||
|
例子
|
||||||
|
```
|
||||||
|
"rsakey":{
|
||||||
|
"privatekey":"$[workdir]$/conf/rsa_private_key.pem",
|
||||||
|
"publickey":"$[workdir]$/conf/rsa_public_key.pem"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 登录控制
|
||||||
|
|
||||||
|
在config.json文件对website属性下
|
||||||
|
### authpaths
|
||||||
|
authpaths是一个数组, 每个元素定义一个路径或路径的起始字符串,在authpaths中定义的路径,用户访问的URL的起始字符串与其相同时需要登录
|
||||||
|
|
||||||
|
例子1
|
||||||
|
```
|
||||||
|
"authpaths":[
|
||||||
|
"/"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
上面的例子整个网站都需要登录后才能访问,这种情况需要设置例外,放出登录界面让用户访问
|
||||||
|
|
||||||
|
例子2
|
||||||
|
```
|
||||||
|
"authparhs":[
|
||||||
|
"/scrent",
|
||||||
|
"/private"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
以上例子定义了需要登录后才能访问的路径,所以以上述两个字符串开始的路径的访问都需要登录
|
||||||
|
|
||||||
|
### whitelist
|
||||||
|
白名单,数组,每项定义一个例外
|
||||||
|
例子1
|
||||||
|
```
|
||||||
|
"whitelist":[
|
||||||
|
"/login",
|
||||||
|
"/login"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
以上例子将根目录下所有以“login”开始的路径都设置成例外,不用登录也可以访问
|
||||||
|
|
||||||
|
## langMapping
|
||||||
|
语言对照
|
||||||
|
不同的浏览器和操作系统,向后台船队的语言差异挺大,这里定义了一个映射
|
||||||
|
|
||||||
|
例子
|
||||||
|
```
|
||||||
|
"langMapping":{
|
||||||
|
"zh-Hans-CN":"zh-cn",
|
||||||
|
"zh-CN":"zh-cn",
|
||||||
|
"en-us":"en",
|
||||||
|
"en-US":"en"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
17
docs/cn/dspy.md
Executable file
17
docs/cn/dspy.md
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
# DspyProcessor
|
||||||
|
dspy处理器处理受限py脚本,应用服务器规定了dspy脚本可以引用的模块,变量,和函数
|
||||||
|
|
||||||
|
## 环境
|
||||||
|
环境是dspy脚本可以引用的一个资源集合,gadget服务器给dspy脚本给定了一个特定的执行环境
|
||||||
|
|
||||||
|
dspy环境请参看[环境](environment.md)
|
||||||
|
|
||||||
|
## 返回
|
||||||
|
dspy 需要使用return 显式给应用服务器返回内容,
|
||||||
|
可以返回的数据类型有:
|
||||||
|
* dict
|
||||||
|
* array
|
||||||
|
* string
|
||||||
|
* RESPONSE 对象及其衍生对象
|
||||||
|
|
||||||
|
|
77
docs/cn/environment.md
Executable file
77
docs/cn/environment.md
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
# 环境
|
||||||
|
gadget服务器给处理器提供了一个执行环境,处理器可以引用环境提供的模块,变量,函数等资源来完成处理器等处理任务
|
||||||
|
|
||||||
|
## 请求相关环境
|
||||||
|
|
||||||
|
### request
|
||||||
|
服务器接收到的服务请求对象,具体内容请参考[AioHttp](https://github.com/aiohttp/aiohttp)
|
||||||
|
|
||||||
|
### params_kw
|
||||||
|
服务器接收到浏览器发送过来的参数,支持各种http(s)方法过来的数据
|
||||||
|
|
||||||
|
### get_user()
|
||||||
|
获得用户id, get_user()是一个协程
|
||||||
|
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
uid = await get_user()
|
||||||
|
```
|
||||||
|
#### 返回值
|
||||||
|
null 或者当前登录用户id
|
||||||
|
|
||||||
|
### remember_user()
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
pwd = password(params_kw.password)
|
||||||
|
ns = {
|
||||||
|
"password":pwd,
|
||||||
|
"name":params_kw.get('name')
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = "select * from users where name=${name}$ and password=${pwd}"
|
||||||
|
x = await sor.sqlExe(sql, ns)
|
||||||
|
if len(x) < 1:
|
||||||
|
return Error(msg="user or password error")
|
||||||
|
userid = x[0].['id']
|
||||||
|
await remember_user(userid)
|
||||||
|
```
|
||||||
|
从前台接收用户名和密码,密码加密后从数据库检索符合名字和密码与参数重相同的用户记录,如果没有找到提示前提错误,否则用户登录成功,服务器记住登录用户的ID
|
||||||
|
|
||||||
|
#### 参数说明
|
||||||
|
##### userid
|
||||||
|
用户id
|
||||||
|
|
||||||
|
##### 返回值
|
||||||
|
无
|
||||||
|
|
||||||
|
### entire_url(url)
|
||||||
|
获得参数URL的绝对url
|
||||||
|
|
||||||
|
#### 例子
|
||||||
|
dspy
|
||||||
|
```
|
||||||
|
url = entire_url('insert.dspy')
|
||||||
|
```
|
||||||
|
template
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"widgettype":"urlwidget",
|
||||||
|
"options":{
|
||||||
|
"url":"{{entire_url('insert.dspy')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### 参数说明
|
||||||
|
##### url
|
||||||
|
相对路径或绝对路径
|
||||||
|
可以上一下形式
|
||||||
|
* http://.....
|
||||||
|
* https://......
|
||||||
|
* /abc/lerltg.tmpl
|
||||||
|
* abc/gggg.html
|
||||||
|
|
||||||
|
##### 返回值
|
||||||
|
带http的路径
|
||||||
|
|
225
docs/cn/sql.md
Executable file
225
docs/cn/sql.md
Executable file
@ -0,0 +1,225 @@
|
|||||||
|
# sqlor
|
||||||
|
|
||||||
|
|
||||||
|
sqlor为web应用提供数据库接口支持, 目前支持的数据库有:
|
||||||
|
* sqlite3
|
||||||
|
* mysql(mariadb)
|
||||||
|
* postgresql
|
||||||
|
* gauss
|
||||||
|
* oracle
|
||||||
|
* sql server
|
||||||
|
|
||||||
|
本模块支持异步DBAPI接口,目前测试过的有aiomysql, aiopg
|
||||||
|
|
||||||
|
## sqlor 优点
|
||||||
|
|
||||||
|
* 支持多种数据库
|
||||||
|
* 支持异步,减少了thread的开销
|
||||||
|
* 自动游标管理
|
||||||
|
* 自动事务管理
|
||||||
|
|
||||||
|
## 典型的应用模式
|
||||||
|
|
||||||
|
## sor 方法
|
||||||
|
|
||||||
|
### C(tablename, ns)
|
||||||
|
向添加表中添加一条记录
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.C(tablename, ns)
|
||||||
|
```
|
||||||
|
#### 参数说明
|
||||||
|
|
||||||
|
##### tablename
|
||||||
|
数据库表名称
|
||||||
|
|
||||||
|
##### ns
|
||||||
|
字典类型的数据,字典的key为自发段名,字典的value是字段值,至少表主键和必须项要求数据
|
||||||
|
|
||||||
|
#### 返回值
|
||||||
|
无
|
||||||
|
|
||||||
|
### R(tablename, ns, filters=None)
|
||||||
|
按要求检索数据,支持分页数据检索和全部数据检索
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
db = DBPools()
|
||||||
|
ns = params_kw.copy()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.R(tablename, ns)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 参数说明
|
||||||
|
##### tablename
|
||||||
|
数据表名
|
||||||
|
##### ns
|
||||||
|
字典数据,参数
|
||||||
|
##### filters
|
||||||
|
缺省为None, 无过滤对象
|
||||||
|
否则是一个字典类型的过滤定义对象
|
||||||
|
#### 返回值
|
||||||
|
* 当ns中没有page属性时,说明返回全部数据,返回数组类型的数据列表
|
||||||
|
* 当ns中有page属性时,返回
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"total":记录数,
|
||||||
|
"rows":[一页的数据记录]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
字典数据
|
||||||
|
|
||||||
|
### U(tablename, ns)
|
||||||
|
修改数据记录,ns中的keys必须包含数据表的primary字段,按照primary字段数据检索并修改数据记录
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
db = DBPools()
|
||||||
|
ns = params_kw.copy()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.U(tablename, ns)
|
||||||
|
```
|
||||||
|
#### 参数说明
|
||||||
|
|
||||||
|
##### tablename
|
||||||
|
数据表名
|
||||||
|
##### ns
|
||||||
|
字典类型的数据,字典的key为自发段名,字典的value是字段值,至少表主键和必须项要求数据
|
||||||
|
#### 返回值
|
||||||
|
无
|
||||||
|
|
||||||
|
### D(tablename, ns)
|
||||||
|
删除数据记录,ns中的keys必须包含数据表的primary字段,按照primary字段数据删除数据表记录
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
db = DBPools()
|
||||||
|
ns = params_kw.copy()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
await sor.D(tablename, ns)
|
||||||
|
```
|
||||||
|
#### 参数说明
|
||||||
|
|
||||||
|
##### tablename
|
||||||
|
数据表名
|
||||||
|
##### ns
|
||||||
|
字典类型的数据,字典的key为自发段名,字典的value是字段值,至少表主键数据
|
||||||
|
#### 返回值
|
||||||
|
无
|
||||||
|
|
||||||
|
### tables()
|
||||||
|
|
||||||
|
### I(tablename)
|
||||||
|
获得表的建表信息
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.I(tablename)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 参数说明
|
||||||
|
##### tablename
|
||||||
|
数据表名
|
||||||
|
|
||||||
|
### 返回值
|
||||||
|
如果数据表不存在,返回None,否则返回字典类型,
|
||||||
|
|
||||||
|
返回值例子
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"summary": [
|
||||||
|
{
|
||||||
|
"name": "user",
|
||||||
|
"title": "\u7528\u6237\u89d2\u8272",
|
||||||
|
"primary": [
|
||||||
|
"id",
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"dec": null,
|
||||||
|
"nullable": "no",
|
||||||
|
"title": "\u7528\u6237id",
|
||||||
|
"table_name": "user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userid",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"dec": null,
|
||||||
|
"nullable": "yes",
|
||||||
|
"title": "\u7528\u6237id",
|
||||||
|
"table_name": "user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rolerd",
|
||||||
|
"type": "str",
|
||||||
|
"length": 32,
|
||||||
|
"dec": null,
|
||||||
|
"nullable": "yes",
|
||||||
|
"title": "\u89d2\u8272id",
|
||||||
|
"table_name": "user"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### sqlExe(sql,ns)
|
||||||
|
执行SQL语句
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
db = DBPools()
|
||||||
|
sql = "select * from mytable id=${id} country=${country}"
|
||||||
|
ns = params_kw.copy()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.sqlExe(sql, ns)
|
||||||
|
```
|
||||||
|
sql中支持使用参数,sqlor使用$[x}$引用变量,sql中的变量需要在ns中能够找到
|
||||||
|
|
||||||
|
#### 参数说明
|
||||||
|
##### sql
|
||||||
|
字符串, 一个合法的sql语句字符串,支持变量,变量用${x}$格式引用ns中的属性.
|
||||||
|
|
||||||
|
##### ns
|
||||||
|
sql语句所需参数字典,如果在ns中找不到sql中引用的变量,将出错
|
||||||
|
|
||||||
|
#### 返回值
|
||||||
|
如果是“select” 语句,返回数组变量,包含所有找到的数据记录
|
||||||
|
其他类型的sql执行结果没有定义
|
||||||
|
|
||||||
|
如果出错,返回None
|
||||||
|
|
||||||
|
### sqlPaging(sql, ns)
|
||||||
|
执行sql,并分页返回数据
|
||||||
|
|
||||||
|
#### 例子
|
||||||
|
```
|
||||||
|
db = DBPools()
|
||||||
|
sql = "select * from mytable id=${id} country=${country}"
|
||||||
|
ns = params_kw.copy()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await sor.sqlPaging(sql, ns)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 参数说明
|
||||||
|
##### sql
|
||||||
|
字符串, 一个合法的sql语句字符串,支持变量,变量用${x}$格式引用ns中的属性.
|
||||||
|
|
||||||
|
##### ns
|
||||||
|
ns中必须包含一个sort属性,说明sql结果数据的排序字段,如果不指定,函数将出错,page参数如果不存在,自动设置为1
|
||||||
|
|
||||||
|
##### 返回值
|
||||||
|
如果sql是一个select语句,返回如下结构数据
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"total":总记录数
|
||||||
|
"rows":[一页的数据]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
0
docs/index.md
Executable file
0
docs/index.md
Executable file
4
macos/build.sh
Executable file
4
macos/build.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
pyinstaller -y --clean gadget.spec
|
||||||
|
cp dist/gadget ~/bin
|
||||||
|
# scp dist/gadget kimird.com:/opt/kymoz/macosx
|
48
macos/gadget.spec
Executable file
48
macos/gadget.spec
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import dataui
|
||||||
|
duipath = os.path.dirname(dataui.__file__)
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(['../src/gadget.py'],
|
||||||
|
pathex=['/Volumes/home/ymq/pydev/github/gadget/src'],
|
||||||
|
binaries=[],
|
||||||
|
datas=[
|
||||||
|
(f'{duipath}/tmpl/bricks/*.*', 'dataui/tmpl/bricks')
|
||||||
|
],
|
||||||
|
hiddenimports=[
|
||||||
|
'sqlite3',
|
||||||
|
'aiopg',
|
||||||
|
'aiomysql'
|
||||||
|
],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False)
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data,
|
||||||
|
cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='gadget',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None )
|
10
requirements.txt
Executable file
10
requirements.txt
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
ahserver
|
||||||
|
sqlor
|
||||||
|
apppublic
|
||||||
|
aiohttp
|
||||||
|
aiohttp-auth-autz
|
||||||
|
openpyxl
|
||||||
|
aiofiles
|
||||||
|
aiohttp_session
|
||||||
|
rsa
|
||||||
|
cryptography
|
51
src/gadget.py
Executable file
51
src/gadget.py
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
from dataui.crud_parser import BricksCRUDProcessor
|
||||||
|
from ahserver.configuredServer import ConfiguredServer
|
||||||
|
|
||||||
|
from appPublic.registerfunction import RegisterFunction
|
||||||
|
from appPublic.objectAction import ObjectAction
|
||||||
|
from appPublic.app_logger import create_logger
|
||||||
|
from appPublic.folderUtils import ProgramPath
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
from appPublic.i18n import getI18N
|
||||||
|
|
||||||
|
from ahserver.filedownload import path_encode
|
||||||
|
from imgThumb import thumb
|
||||||
|
from idfile import idFileDownload
|
||||||
|
# from myauth import MyAuthAPI
|
||||||
|
from rf import getPublicKey, getI18nMapping
|
||||||
|
from loadplugins import load_plugins
|
||||||
|
from version import __version__
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(prog="Gadget")
|
||||||
|
parser.add_argument('-w', '--workdir')
|
||||||
|
parser.add_argument('-p', '--port')
|
||||||
|
args = parser.parse_args()
|
||||||
|
workdir = args.workdir or os.getcwd()
|
||||||
|
p = ProgramPath()
|
||||||
|
print(workdir)
|
||||||
|
config = getConfig(workdir, NS={'workdir':workdir, 'ProgramPath':p})
|
||||||
|
if config.logger:
|
||||||
|
logger = create_logger(config.logger.name or 'gadget',
|
||||||
|
levelname=config.logger.levelname or 'debug',
|
||||||
|
file=config.logger.logfile or None)
|
||||||
|
else:
|
||||||
|
logger = create_logger('gadget', levelname='debug')
|
||||||
|
rf = RegisterFunction()
|
||||||
|
rf.register('makeThumb',thumb)
|
||||||
|
rf.register('idFileDownload',idFileDownload)
|
||||||
|
rf.register('getPublicKey', getPublicKey)
|
||||||
|
rf.register('getI18nMapping', getI18nMapping)
|
||||||
|
|
||||||
|
i18n = getI18N(path=workdir)
|
||||||
|
# server = ConfiguredServer(auth_klass=MyAuthAPI, workdir=workdir)
|
||||||
|
server = ConfiguredServer(workdir=workdir)
|
||||||
|
load_plugins(workdir)
|
||||||
|
logger.info(f'gadget version={__version__}')
|
||||||
|
logger.debug(f'debug mode show ?')
|
||||||
|
port = args.port or config.website.port or 8080
|
||||||
|
port = int(port)
|
||||||
|
server.run(port=port)
|
9
src/gadgetstart.sh
Normal file
9
src/gadgetstart.sh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
start_gadget()
|
||||||
|
{
|
||||||
|
while [ "1" = "1" ]
|
||||||
|
do
|
||||||
|
cd /d/gadget
|
||||||
|
bin/gadget >gadget.out 2>&1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
start_gadget &
|
12
src/id2file.py
Executable file
12
src/id2file.py
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
from sqlor.dbpools import runSQL
|
||||||
|
|
||||||
|
async def getFilenameFromId(idstr:str) -> str:
|
||||||
|
sql = "select * from kvobjects where id='%s'" % idstr
|
||||||
|
recs = await runSQL('homedata',sql)
|
||||||
|
if recs is None:
|
||||||
|
return None
|
||||||
|
if len(recs) == 0:
|
||||||
|
return None
|
||||||
|
return recs[0].name
|
||||||
|
|
30
src/idfile.py
Normal file
30
src/idfile.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import os
|
||||||
|
from PIL import Image, ExifTags
|
||||||
|
from io import BytesIO
|
||||||
|
from aiohttp.web_exceptions import (
|
||||||
|
HTTPException,
|
||||||
|
HTTPExpectationFailed,
|
||||||
|
HTTPForbidden,
|
||||||
|
HTTPMethodNotAllowed,
|
||||||
|
HTTPNotFound,
|
||||||
|
)
|
||||||
|
from aiohttp.web_response import Response, StreamResponse
|
||||||
|
|
||||||
|
from appPublic.registerfunction import RegisterFunction
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
from ahserver.filedownload import file_download
|
||||||
|
from id2file import getFilenameFromId
|
||||||
|
|
||||||
|
def www_abspath(fp):
|
||||||
|
if fp[0] == '/':
|
||||||
|
fp = fp[1:]
|
||||||
|
config = getConfig()
|
||||||
|
return os.path.join(config.filesroot, fp)
|
||||||
|
|
||||||
|
async def idFileDownload(*args, **kw):
|
||||||
|
print(f'idFileDownload(): {args=}, {kw=}')
|
||||||
|
fname = kw.get('path', None)
|
||||||
|
path = www_abspath(fname)
|
||||||
|
print(f'{fname=}, {path=}')
|
||||||
|
request = kw.get('request')
|
||||||
|
return await file_download(request,path)
|
82
src/imgThumb.py
Executable file
82
src/imgThumb.py
Executable file
@ -0,0 +1,82 @@
|
|||||||
|
import os
|
||||||
|
from PIL import Image, ExifTags
|
||||||
|
from io import BytesIO
|
||||||
|
from aiohttp.web_exceptions import (
|
||||||
|
HTTPException,
|
||||||
|
HTTPExpectationFailed,
|
||||||
|
HTTPForbidden,
|
||||||
|
HTTPMethodNotAllowed,
|
||||||
|
HTTPNotFound,
|
||||||
|
)
|
||||||
|
from aiohttp.web_response import Response, StreamResponse
|
||||||
|
from id2file import getFilenameFromId
|
||||||
|
|
||||||
|
|
||||||
|
def imageUp(img):
|
||||||
|
try:
|
||||||
|
o = 'Orientation'
|
||||||
|
exif=dict(img._getexif().items())
|
||||||
|
if exif[o] == 3:
|
||||||
|
img = img.rotate(180, expand=True)
|
||||||
|
elif exif[o] == 6:
|
||||||
|
img = img.rotate(270, expand=True)
|
||||||
|
elif exif[o] == 8:
|
||||||
|
img = img.rotate(90, expand=True)
|
||||||
|
return img
|
||||||
|
except (AttributeError, KeyError, IndexError):
|
||||||
|
# cases: image don't have getexif
|
||||||
|
return img
|
||||||
|
|
||||||
|
def imageThumb(imgfilepath,width=None,height=None):
|
||||||
|
im = Image.open(imgfilepath)
|
||||||
|
im = imageUp(im)
|
||||||
|
mode = im.mode
|
||||||
|
if mode not in ('L', 'RGB'):
|
||||||
|
if mode == 'RGBA':
|
||||||
|
alpha = im.split()[3]
|
||||||
|
bgmask = alpha.point(lambda x: 255-x)
|
||||||
|
im = im.convert('RGB')
|
||||||
|
# paste(color, box, mask)
|
||||||
|
im.paste((255,255,255), None, bgmask)
|
||||||
|
else:
|
||||||
|
im = im.convert('RGB')
|
||||||
|
|
||||||
|
w, h = im.size
|
||||||
|
if not width and not height:
|
||||||
|
width = 256
|
||||||
|
if width:
|
||||||
|
width = int(width)
|
||||||
|
height = int(float(width) * float(h) / float(w))
|
||||||
|
else:
|
||||||
|
height = int(height)
|
||||||
|
width = int(float(height) * float(w) / float(h))
|
||||||
|
thumb = im.resize((width,height),Image.ANTIALIAS)
|
||||||
|
f = BytesIO()
|
||||||
|
thumb.save(f,format='jpeg',quality=60)
|
||||||
|
im.close()
|
||||||
|
v = f.getvalue()
|
||||||
|
return v
|
||||||
|
|
||||||
|
async def thumb(*args, **kw):
|
||||||
|
id = args[0]
|
||||||
|
request = kw.get('request')
|
||||||
|
xpath = request.path[len(options.leading):]
|
||||||
|
if xpath == '':
|
||||||
|
raise HTTPNotFound
|
||||||
|
id = xpath[1:]
|
||||||
|
imgpath = await getFilenameFromId(id)
|
||||||
|
v = imageThumb(imgpath,width=options.width,height=options.height)
|
||||||
|
response = Response(
|
||||||
|
status=200,
|
||||||
|
headers = {
|
||||||
|
'Content-Disposition': 'attrachment;filename={}'.format(os.path.basename(imgpath)),
|
||||||
|
'Content-Length':str(len(v))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await response.prepare(request)
|
||||||
|
await response.write(v)
|
||||||
|
await response.write_eof()
|
||||||
|
return response
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
imageThumb("/home/ymq/media/pictures/2019-08/IMG_20190804_113014.jpg", width=256)
|
17
src/loadplugins.py
Executable file
17
src/loadplugins.py
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from appPublic.folderUtils import listFile
|
||||||
|
from appPublic.ExecFile import ExecFile
|
||||||
|
|
||||||
|
def load_plugins(p_dir):
|
||||||
|
ef = ExecFile()
|
||||||
|
pdir = os.path.join(p_dir, 'plugins')
|
||||||
|
if not os.path.isdir(pdir):
|
||||||
|
print('load_plugins:%s not exists' % pdir)
|
||||||
|
return
|
||||||
|
sys.path.append(pdir)
|
||||||
|
for py in listFile(pdir, suffixs=['.py'], rescursive=True):
|
||||||
|
ef.set('sys',sys)
|
||||||
|
ef.run(py)
|
||||||
|
|
37
src/myauth.py
Executable file
37
src/myauth.py
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
from ahserver.auth_api import AuthAPI
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
from appPublic.registerfunction import getRegisterFunctionByName
|
||||||
|
|
||||||
|
class MyAuthAPI(AuthAPI):
|
||||||
|
async def needAuth(self,path):
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def checkUserPermission(self, user, path):
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def checkUserPassword(self,user_id,password):
|
||||||
|
config = getConfig()
|
||||||
|
if config.users:
|
||||||
|
for userid, passwd in config.users.items():
|
||||||
|
if user_id == userid and password == passwd:
|
||||||
|
print('******user passwd check OK****************')
|
||||||
|
return True
|
||||||
|
rf = getRegisterFunctionByName('user_password_check')
|
||||||
|
if rf:
|
||||||
|
return rf(user_id, password)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def getPermissionNeed(self,path):
|
||||||
|
rf = getRegisterFunctionByName('get_need_permission')
|
||||||
|
if rf:
|
||||||
|
return rf(path)
|
||||||
|
|
||||||
|
return 'ok'
|
||||||
|
|
||||||
|
async def getUserPermissions(self,user):
|
||||||
|
rf = getRegisterFunctionByName('get_user_permissions')
|
||||||
|
if rf:
|
||||||
|
return rf(user)
|
||||||
|
|
||||||
|
return ['ok']
|
16
src/rf.py
Executable file
16
src/rf.py
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
from appPublic.i18n import getI18N
|
||||||
|
from ahserver.filedownload import file_download
|
||||||
|
|
||||||
|
async def getPublicKey(*args, **kw):
|
||||||
|
config = getConfig()
|
||||||
|
request = kw.get('request')
|
||||||
|
pf = config.website.rsakey.publickey
|
||||||
|
return await file_download(request,pf)
|
||||||
|
|
||||||
|
async def getI18nMapping(*args, **kw):
|
||||||
|
lang = args[0]
|
||||||
|
i18n = getI18N()
|
||||||
|
mapping = i18n.getLangDict(lang)
|
||||||
|
return mapping
|
||||||
|
|
1
src/version.py
Executable file
1
src/version.py
Executable file
@ -0,0 +1 @@
|
|||||||
|
__version__ = '1.0.1'
|
48
test/conf/config.json
Executable file
48
test/conf/config.json
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"logger":{
|
||||||
|
"name":"gadget",
|
||||||
|
"levelname":"debug",
|
||||||
|
"logfile":"$[workdir]$/logs/gadget.log"
|
||||||
|
},
|
||||||
|
"filesroot":"$[workdir]$/files",
|
||||||
|
"website":{
|
||||||
|
"paths":[
|
||||||
|
["$[workdir]$/wwwroot",""]
|
||||||
|
],
|
||||||
|
"host":"0.0.0.0",
|
||||||
|
"port":9080,
|
||||||
|
"coding":"utf-8",
|
||||||
|
"ssl_gg":{
|
||||||
|
"crtfile":"$[workdir]$/conf/www.bsppo.com.pem",
|
||||||
|
"keyfile":"$[workdir]$/conf/www.bsppo.com.key"
|
||||||
|
},
|
||||||
|
"indexes":[
|
||||||
|
"index.html",
|
||||||
|
"index.tmpl",
|
||||||
|
"index.dspy",
|
||||||
|
"index.md"
|
||||||
|
],
|
||||||
|
"processors":[
|
||||||
|
[".proxy","proxy"],
|
||||||
|
[".xlsxds","xlsxds"],
|
||||||
|
[".sqlds","sqlds"],
|
||||||
|
[".tmpl.js","tmpl"],
|
||||||
|
[".tmpl.css","tmpl"],
|
||||||
|
[".html.tmpl","tmpl"],
|
||||||
|
[".tmpl","tmpl"],
|
||||||
|
[".ui","tmpl"],
|
||||||
|
[".dspy","dspy"],
|
||||||
|
[".md","md"]
|
||||||
|
],
|
||||||
|
"rsakey":{
|
||||||
|
"privatekey":"$[workdir]$/conf/rsa_private_key.pem",
|
||||||
|
"publickey":"$[workdir]$/conf/rsa_public_key.pem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"langMapping":{
|
||||||
|
"zh-Hans-CN":"zh-cn",
|
||||||
|
"zh-CN":"zh-cn",
|
||||||
|
"en-us":"en",
|
||||||
|
"en-US":"en"
|
||||||
|
}
|
||||||
|
}
|
0
test/files/README.md
Executable file
0
test/files/README.md
Executable file
0
test/i18n/en_US/msg.txt
Executable file
0
test/i18n/en_US/msg.txt
Executable file
0
test/i18n/zh_CN/msg.txt
Executable file
0
test/i18n/zh_CN/msg.txt
Executable file
0
test/logs/README.md
Executable file
0
test/logs/README.md
Executable file
2
test/logs/gadget.log
Normal file
2
test/logs/gadget.log
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
2023-02-23 15:51:57,018[gadget][INFO][gadget.py:60]gadget version=1.0.1
|
||||||
|
2023-02-23 15:51:57,018[gadget][DEBUG][gadget.py:61]debug mode show ?
|
7
test/wwwroot/index.html
Executable file
7
test/wwwroot/index.html
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Welcome to gadget</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
ubuntu/build.sh
Executable file
1
ubuntu/build.sh
Executable file
@ -0,0 +1 @@
|
|||||||
|
pyinstaller -y --clean gadget.spec
|
15
ubuntu/gadget.service
Normal file
15
ubuntu/gadget.service
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Gadget Webserver base on aiothhp
|
||||||
|
After=network.target mariadb.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
# EnvironmentFile=-/etc/default/gadget
|
||||||
|
ExecStart=/d/gadget/bin/start.sh
|
||||||
|
ExecStop=/usr/bin/killall gadget
|
||||||
|
KillMode=process
|
||||||
|
RuntimeDirectory=/d/gadget
|
||||||
|
RuntimeDirectoryMode=0755
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
Alias=gadget.service
|
48
ubuntu/gadget.spec
Executable file
48
ubuntu/gadget.spec
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import dataui
|
||||||
|
duipath = os.path.dirname(dataui.__file__)
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(['../src/gadget.py'],
|
||||||
|
pathex=['/Volumes/home/ymq/pydev/github/gadget/src'],
|
||||||
|
binaries=[],
|
||||||
|
datas=[
|
||||||
|
(f'{duipath}/tmpl/bricks/*.*', 'dataui/tmpl/bricks')
|
||||||
|
],
|
||||||
|
hiddenimports=[
|
||||||
|
'sqlite3',
|
||||||
|
'aiopg',
|
||||||
|
'aiomysql'
|
||||||
|
],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False)
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data,
|
||||||
|
cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='gadget',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None )
|
3
windows/build.cmd
Executable file
3
windows/build.cmd
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
pyinstaller -y --clean gadget.spec
|
||||||
|
copy dist\gadget.exe c:\cmds
|
||||||
|
|
48
windows/gadget.spec
Executable file
48
windows/gadget.spec
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import dataui
|
||||||
|
duipath = os.path.dirname(dataui.__file__)
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(['../src/gadget.py'],
|
||||||
|
pathex=['/Volumes/home/ymq/pydev/github/gadget/src'],
|
||||||
|
binaries=[],
|
||||||
|
datas=[
|
||||||
|
(f'{duipath}/tmpl/bricks/*.*', 'dataui/tmpl/bricks')
|
||||||
|
],
|
||||||
|
hiddenimports=[
|
||||||
|
'sqlite3',
|
||||||
|
'aiopg',
|
||||||
|
'aiomysql'
|
||||||
|
],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False)
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data,
|
||||||
|
cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='gadget',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=True,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None )
|
Loading…
Reference in New Issue
Block a user