first commit

This commit is contained in:
yumoqing 2025-07-16 14:20:27 +08:00
commit d1fa283dc4
22 changed files with 496 additions and 0 deletions

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# uapi
universe api

3
json/build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/bash
xls2ui -m ../models -o ../wwwroot appbase *.json

37
json/uapi.json Normal file
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

BIN
models/uapiset.xlsx Normal file

Binary file not shown.

BIN
models/upapp.xlsx Normal file

Binary file not shown.

BIN
models/upappapis.xlsx Normal file

Binary file not shown.

BIN
models/upappkey.xlsx Normal file

Binary file not shown.

4
pyproject.toml Normal file
View File

@ -0,0 +1,4 @@
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
aiohttp

40
script/perms.json Normal file
View 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
View 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

Binary file not shown.

32
test/t.py Normal file
View 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
View File

238
uapi/appapi.py Normal file
View 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
View File

@ -0,0 +1,7 @@
from ahserver.serverenv import ServerEnv
from .appapi import UAPI
def load_uapi():
g = ServerEnv()
g.UAPI = UAPI