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