first commit
This commit is contained in:
commit
d1fa283dc4
3
json/build.sh
Executable file
3
json/build.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
xls2ui -m ../models -o ../wwwroot appbase *.json
|
||||||
37
json/uapi.json
Normal file
37
json/uapi.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"tblname":"uapi",
|
||||||
|
"params":{
|
||||||
|
"toolbar":{
|
||||||
|
"tools":[
|
||||||
|
{
|
||||||
|
"selected_row":true,
|
||||||
|
"name":"test",
|
||||||
|
"icon":"{{entire_url('/imgs/test.svg')}}",
|
||||||
|
"label": "api测试"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"binds":[
|
||||||
|
{
|
||||||
|
"wid":"self",
|
||||||
|
"event":"test",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"target":"PopupWindow",
|
||||||
|
"options":{
|
||||||
|
"methid":"POST",
|
||||||
|
"params":{},
|
||||||
|
"url":"{{entire_url('/uapi/uapi_test.ui')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"title":"API",
|
||||||
|
"description":"API定义",
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields":{
|
||||||
|
"exclouded":["id"],
|
||||||
|
"alters":{}
|
||||||
|
},
|
||||||
|
"editexclouded":["id", "usid"]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
json/uapiset.json
Normal file
20
json/uapiset.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"tblname":"uapiset",
|
||||||
|
"params":{
|
||||||
|
"title":"API接口集",
|
||||||
|
"description":"支持的所有API接口",
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields":{
|
||||||
|
"exclouded":["id"],
|
||||||
|
"alters":{}
|
||||||
|
},
|
||||||
|
"editexclouded":["id"],
|
||||||
|
"subtables":[
|
||||||
|
{
|
||||||
|
"field":"usid",
|
||||||
|
"subtable": "uapi",
|
||||||
|
"title": "API"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
25
json/upapp.json
Normal file
25
json/upapp.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"tblname":"upapp",
|
||||||
|
"params":{
|
||||||
|
"title":"上位系统",
|
||||||
|
"description":"上位系统",
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields":{
|
||||||
|
"exclouded":["id"],
|
||||||
|
"alters":{}
|
||||||
|
},
|
||||||
|
"editexclouded":["id"],
|
||||||
|
"subtables":[
|
||||||
|
{
|
||||||
|
"field":"upappid",
|
||||||
|
"subtable": "upappapis",
|
||||||
|
"title": "API集"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field":"upappid",
|
||||||
|
"subtable": "upappkey",
|
||||||
|
"title": "APIKEY"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
json/upapp1.json
Normal file
20
json/upapp1.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"tblname":"upapp",
|
||||||
|
"alias":"upapp1",
|
||||||
|
"params":{
|
||||||
|
"title":"上位系统",
|
||||||
|
"sortby":"name",
|
||||||
|
"noedit":true,
|
||||||
|
"browserfields":{
|
||||||
|
"exclouded":["id"],
|
||||||
|
"alters":{}
|
||||||
|
},
|
||||||
|
"subtables":[
|
||||||
|
{
|
||||||
|
"field":"upappid",
|
||||||
|
"subtable":"upappkey",
|
||||||
|
"title":"应用密码"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
33
json/upappapis.json
Normal file
33
json/upappapis.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"tblname":"upappapis",
|
||||||
|
"params":{
|
||||||
|
"toolbar":{
|
||||||
|
"tools":[
|
||||||
|
{
|
||||||
|
"name":"test",
|
||||||
|
"icon":"{{entire_url('/imgs/test.svg')}}",
|
||||||
|
"label": "测试",
|
||||||
|
"selected_row":true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"binds":[
|
||||||
|
{
|
||||||
|
"wid":"self",
|
||||||
|
"event":"test",
|
||||||
|
"actiontype": "urlwidget",
|
||||||
|
"options":{
|
||||||
|
"url":"{{entire_url('/uapi/apitest.dspy')}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title":"使用api集",
|
||||||
|
"description":"此系统使用的API集",
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields":{
|
||||||
|
"exclouded":["id"],
|
||||||
|
"alters":{}
|
||||||
|
},
|
||||||
|
"editexclouded":["id", "upappid"]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
json/upappkey.json
Normal file
14
json/upappkey.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"tblname":"upappkey",
|
||||||
|
"params":{
|
||||||
|
"title":"上位系统密码",
|
||||||
|
"logined_userorgid":"ownerid",
|
||||||
|
"description":"上位系统密码",
|
||||||
|
"confidential_fields":["apikey", "apipasswd" ],
|
||||||
|
"browserfields":{
|
||||||
|
"exclouded":["id", "ownerid"],
|
||||||
|
"alters":{}
|
||||||
|
},
|
||||||
|
"editexclouded":["id", "upappid", "ownerid"]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
models/uapi.xlsx
Normal file
BIN
models/uapi.xlsx
Normal file
Binary file not shown.
BIN
models/uapiset.xlsx
Normal file
BIN
models/uapiset.xlsx
Normal file
Binary file not shown.
BIN
models/upapp.xlsx
Normal file
BIN
models/upapp.xlsx
Normal file
Binary file not shown.
BIN
models/upappapis.xlsx
Normal file
BIN
models/upappapis.xlsx
Normal file
Binary file not shown.
BIN
models/upappkey.xlsx
Normal file
BIN
models/upappkey.xlsx
Normal file
Binary file not shown.
4
pyproject.toml
Normal file
4
pyproject.toml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
aiohttp
|
||||||
40
script/perms.json
Normal file
40
script/perms.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"path": "/uapi/upapp",
|
||||||
|
"perms": [
|
||||||
|
{
|
||||||
|
"orgtype": "customer",
|
||||||
|
"roles":["operator"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orgtype": "owner",
|
||||||
|
"roles":["operator"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/uapi/jsonhttpapi",
|
||||||
|
"perms": [
|
||||||
|
{
|
||||||
|
"orgtype": "customer",
|
||||||
|
"roles":["operator"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orgtype": "owner",
|
||||||
|
"roles":["operator"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path":"/uapi/upappkey",
|
||||||
|
"perm":[
|
||||||
|
{
|
||||||
|
"orgtype": "customer",
|
||||||
|
"roles":["operator"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orgtype": "owner",
|
||||||
|
"roles":["operator"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
setup.cfg
Normal file
19
setup.cfg
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# setup.cfg
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
name=uapi
|
||||||
|
version = 0.0.1
|
||||||
|
description = Your project description
|
||||||
|
author = "yu moqing"
|
||||||
|
author_email = "yumoqing@gmail.com"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[options]
|
||||||
|
packages = find:
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
install_requires =
|
||||||
|
apppublic
|
||||||
|
sqlor
|
||||||
|
ahserver
|
||||||
|
|
||||||
BIN
test/.t.py.swp
Normal file
BIN
test/.t.py.swp
Normal file
Binary file not shown.
32
test/t.py
Normal file
32
test/t.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
from uapi.appapi import UAPI
|
||||||
|
|
||||||
|
def get_module_dbname(mn):
|
||||||
|
return 'sage'
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
workdir = os.getcwd()
|
||||||
|
config = getConfig(workdir, {'workdir':workdir})
|
||||||
|
DBPools(config.databases)
|
||||||
|
env = ServerEnv()
|
||||||
|
env.get_module_dbname = get_module_dbname
|
||||||
|
uapi = UAPI()
|
||||||
|
params = {
|
||||||
|
'baseurl':'https://qianfan.baidubce.com',
|
||||||
|
'model':'deepseek-v3',
|
||||||
|
'prompt':'北京今天天气如何,适合跑步吗?',
|
||||||
|
}
|
||||||
|
upapiid = 'R47xUJay76dCCt1sLmWvE' # 百度智能搜索
|
||||||
|
# MRXYc49LwTjyTLuOVk89R 百度文生文
|
||||||
|
|
||||||
|
async for line in uapi(upapiid, '0', params=params):
|
||||||
|
print(line)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.new_event_loop().run_until_complete(main())
|
||||||
|
|
||||||
0
uapi/__init__.py
Normal file
0
uapi/__init__.py
Normal file
238
uapi/appapi.py
Normal file
238
uapi/appapi.py
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from traceback import format_exc
|
||||||
|
from functools import partial
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from appPublic.streamhttpclient import StreamHttpClient, liner
|
||||||
|
from appPublic.dictObject import DictObject
|
||||||
|
from appPublic.myTE import MyTemplateEngine
|
||||||
|
from appPublic.log import debug, exception, error
|
||||||
|
from ahserver.globalEnv import password_decode
|
||||||
|
from ahserver.serverenv import get_serverenv
|
||||||
|
|
||||||
|
def get_dbname():
|
||||||
|
f = get_serverenv('get_module_dbname')
|
||||||
|
dbname = f('uapi')
|
||||||
|
return dbname
|
||||||
|
|
||||||
|
class UAPI:
|
||||||
|
def __init__(self, env={}):
|
||||||
|
self.te = MyTemplateEngine([], env=env)
|
||||||
|
self.env = env
|
||||||
|
self.auth_api = None
|
||||||
|
self.auth_ret = None
|
||||||
|
|
||||||
|
async def rendertmpl(self, tmplstr, params={}):
|
||||||
|
if tmplstr is None:
|
||||||
|
return None
|
||||||
|
ns = self.env.copy()
|
||||||
|
ns.update(params)
|
||||||
|
te = MyTemplateEngine([], env=self.env)
|
||||||
|
return te.renders(tmplstr, ns)
|
||||||
|
|
||||||
|
async def cvt_upappapi2uapi(self, upappapiid, callerid, params={}):
|
||||||
|
self.env.update(params)
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_dbname()
|
||||||
|
uapi = None
|
||||||
|
auth_uapi = None
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
upappapi = await self.get_upappapis(sor, upappapiid)
|
||||||
|
kinfo = await self.get_orgapikey(sor, upappapi.upappid, callerid)
|
||||||
|
self.env.update(kinfo)
|
||||||
|
auth_uapi = None
|
||||||
|
if kinfo.auth_apiid:
|
||||||
|
auth_upapi = await self.get_upappapis(sor, kinfo.auth_apiid)
|
||||||
|
auth_uapi = await self.get_uapi(sor, kinfo.auth_upapi.uapiid)
|
||||||
|
uapi = await self.get_uapi(sor, upappapi.uapiid)
|
||||||
|
return auth_uapi, uapi
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
async def __call__(self, upapiid, callerid, params={}):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
auth_uapi, uapi = await self.cvt_upappapi2uapi(upapiid,
|
||||||
|
callerid, params=params)
|
||||||
|
if uapi is None:
|
||||||
|
return
|
||||||
|
if auth_uapi:
|
||||||
|
await self.do_auth(auth_uapi)
|
||||||
|
async for line in self.stream_gen(uapi):
|
||||||
|
yield line
|
||||||
|
|
||||||
|
async def request(self, upapiid, callerid, params={}):
|
||||||
|
auth_uapi, uapi = await self.cvt_upappapi2uapi(upapiid,
|
||||||
|
callerid, params=params)
|
||||||
|
if auth_uapi:
|
||||||
|
await self.do_auth(auth_uapi)
|
||||||
|
return await self.do_call(uapi)
|
||||||
|
|
||||||
|
async def do_auth(self, auth_uapi):
|
||||||
|
b = await self.do_call(auth_uapi)
|
||||||
|
d = json.loads(b.encode('utf-8'))
|
||||||
|
self.env.update(d)
|
||||||
|
return
|
||||||
|
|
||||||
|
async def do_call(self, api, params={}):
|
||||||
|
url = self.env.get('baseurl') + api.path
|
||||||
|
method = api.httpmethod
|
||||||
|
header = await self.rendertmpl(api.headers)
|
||||||
|
header = json.loads(headers)
|
||||||
|
body = await self.rendertmpl(api.body)
|
||||||
|
_params = await self.rendertmpl(api.params)
|
||||||
|
if _params:
|
||||||
|
_params = json.loads(_params)
|
||||||
|
shc = StreamHttpClient()
|
||||||
|
b = await shc.request(method, url,
|
||||||
|
headers=headers,
|
||||||
|
data=body,
|
||||||
|
params=_params)
|
||||||
|
|
||||||
|
d = json.loads(b.encode('utf-8'))
|
||||||
|
if api.response:
|
||||||
|
return await self.rendertmpl(api.response, params=d)
|
||||||
|
return d
|
||||||
|
|
||||||
|
async def stream_gen(self, api, params={}):
|
||||||
|
url = self.env.get('baseurl') + api.path
|
||||||
|
method = api.httpmethod
|
||||||
|
headers = await self.rendertmpl(api.headers)
|
||||||
|
headers = json.loads(headers)
|
||||||
|
body = await self.rendertmpl(api.data)
|
||||||
|
if body:
|
||||||
|
bdy = json.loads(body)
|
||||||
|
bdy['stream'] = True
|
||||||
|
body = json.dumps(bdy, ensure_ascii=False)
|
||||||
|
_params = await self.rendertmpl(api.params)
|
||||||
|
if _params:
|
||||||
|
_params = json.loads(_params)
|
||||||
|
debug(f'{headers=}, {body=}. {method=}, {url=}')
|
||||||
|
shc = StreamHttpClient()
|
||||||
|
gen = shc(method, url,
|
||||||
|
headers=headers,
|
||||||
|
data=body,
|
||||||
|
params=_params)
|
||||||
|
chunk_match = api.chunk_match or ''
|
||||||
|
cmlen = len(chunk_match)
|
||||||
|
async for line in liner(gen):
|
||||||
|
line = line.decode('utf-8')
|
||||||
|
if line.startswith(chunk_match):
|
||||||
|
line = line[cmlen:]
|
||||||
|
cvt_line = await self.streamline_handle(line,
|
||||||
|
api.streamresponse)
|
||||||
|
if cvt_line is not None:
|
||||||
|
yield cvt_line
|
||||||
|
else:
|
||||||
|
debug(f'{line=} after convert is None')
|
||||||
|
else:
|
||||||
|
debug(f'{chunk_match=},{line=} not matched')
|
||||||
|
|
||||||
|
async def streamline_handle(self, line, resptmpl):
|
||||||
|
try:
|
||||||
|
dic = json.loads(line)
|
||||||
|
if resptmpl:
|
||||||
|
jstr = await self.rendertmpl(resptmpl, params=dic)
|
||||||
|
jstr += '\n'
|
||||||
|
else:
|
||||||
|
jstr = json.dumps(dic, ensure_ascii=False) + '\n'
|
||||||
|
return jstr
|
||||||
|
except Exception as e:
|
||||||
|
exception(f'{line=}\n{e=},{format_exc()}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_upappapis(self, sor, upapiid):
|
||||||
|
recs = await sor.R('upappapis', {'id': upapiid})
|
||||||
|
if len(recs) < 1:
|
||||||
|
e = Exception(f'{upapiid} not found in table(upappapis)')
|
||||||
|
exception(f'{e}\n{format_exc()}')
|
||||||
|
raise e
|
||||||
|
return recs[0]
|
||||||
|
|
||||||
|
async def get_uapi(self, sor, uapiid):
|
||||||
|
recs = await sor.R('uapi', {'id': uapiid})
|
||||||
|
if len(recs) < 1:
|
||||||
|
e = Exception(f'{uapiid} not found in table(uapi)')
|
||||||
|
exception(f'{e}\n{format_exc()}')
|
||||||
|
raise e
|
||||||
|
return recs[0]
|
||||||
|
|
||||||
|
async def get_orgapikey(self, sor, upappid, callerid):
|
||||||
|
"""
|
||||||
|
argumemts:
|
||||||
|
upappid: upappid which will make call to
|
||||||
|
orgid: owner organization or user which as the caller of the call
|
||||||
|
return:
|
||||||
|
None: this orgid has not gotton apikey from upapp
|
||||||
|
dict if apikey and upapp infos
|
||||||
|
"""
|
||||||
|
sql = """select
|
||||||
|
a.myappid,
|
||||||
|
a.apisetid,
|
||||||
|
a.auth_apiid,
|
||||||
|
a.baseurl,
|
||||||
|
b.*
|
||||||
|
from upapp a, upappkey b
|
||||||
|
where a.id = b.upappid
|
||||||
|
and a.id = ${appid}$
|
||||||
|
and b.ownerid = ${orgid}$"""
|
||||||
|
recs = await sor.sqlExe(sql, {'appid':upappid, 'orgid': callerid})
|
||||||
|
if len(recs) < 1:
|
||||||
|
e = Exception(f'{appid=}, {callerid=} has not apikey')
|
||||||
|
exception(f'{e}, {format_exc()}')
|
||||||
|
raise e
|
||||||
|
r = recs[0]
|
||||||
|
return DictObject(**{
|
||||||
|
'apikey':password_decode(r.apikey),
|
||||||
|
'secretkey':password_decode(r.secretkey),
|
||||||
|
'apisetid': r.apisetid,
|
||||||
|
'auth_apiid': r.auth_apiid,
|
||||||
|
'myappid': r.myappid
|
||||||
|
})
|
||||||
|
|
||||||
|
async def get_authapi(self, sor):
|
||||||
|
sql = "select * from jsonhttpapi where name = 'auth' and appid=${appid}$"
|
||||||
|
recs = await sor.sqlExe(sql, {'appid': self.appid})
|
||||||
|
if len(recs):
|
||||||
|
return recs[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_apiinfo(self, orgids, apiname):
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_serverenv('get_module_dbname')('uapi')
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
sql = """select c.baseurl,
|
||||||
|
c.name as appname,
|
||||||
|
b.*,
|
||||||
|
a.apikey,
|
||||||
|
a.secretkey
|
||||||
|
from upappkey a, jsonhttpapi b, upapp c
|
||||||
|
where
|
||||||
|
a.upappid=b.appid
|
||||||
|
and a.upappid = c.id
|
||||||
|
and b.appid = ${appid}$
|
||||||
|
and b.name = ${apiname}$
|
||||||
|
and a.ownerid = ${orgid}$
|
||||||
|
"""
|
||||||
|
for orgid in orgids:
|
||||||
|
ns = {
|
||||||
|
"orgid": orgid,
|
||||||
|
"apiname": appname,
|
||||||
|
"appid": self.appid
|
||||||
|
}
|
||||||
|
recs = await sor.sqlExe(sql, ns)
|
||||||
|
if len(recs) > 0:
|
||||||
|
rec = recs[0]
|
||||||
|
rec.apikey = password_decode(rec.apikey)
|
||||||
|
if rec.secretkey:
|
||||||
|
rec.secretkey = password_decode(rec.secretkey)
|
||||||
|
if rec.need_auth and self.auth_api is None:
|
||||||
|
self.auth_api = await self.get_authapi(sor)
|
||||||
|
if rec.need_auth and self.auth_api is None:
|
||||||
|
e = Exception(f'{rec.appname} app has not auth api but need')
|
||||||
|
exception(f'{e=}, {format_exc()}')
|
||||||
|
raise e
|
||||||
|
rec.auth_api = self.auth_api
|
||||||
|
rec.apikey_orgid = orgid
|
||||||
|
return recs[0]
|
||||||
|
return None
|
||||||
|
|
||||||
7
uapi/init.py
Normal file
7
uapi/init.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
from .appapi import UAPI
|
||||||
|
|
||||||
|
def load_uapi():
|
||||||
|
g = ServerEnv()
|
||||||
|
g.UAPI = UAPI
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user