This commit is contained in:
yumoqing 2025-07-08 18:55:21 +08:00
parent 66f16d3e87
commit 54109828b6
12 changed files with 201 additions and 143 deletions

13
json/uapi.json Normal file
View File

@ -0,0 +1,13 @@
{
"tblname":"uapi",
"params":{
"title":"API",
"description":"API定义",
"sortby":"name",
"browserfields":{
"exclouded":["id"],
"alters":{}
},
"editexclouded":["id", "usid"]
}
}

View File

@ -8,6 +8,13 @@
"exclouded":["id"],
"alters":{}
},
"editexclouded":["id"]
"editexclouded":["id"],
"subtables":[
{
"field":"uapiid",
"subtable": "uapi",
"title": "API"
}
]
}
}

View File

@ -8,6 +8,18 @@
"exclouded":["id"],
"alters":{}
},
"editexclouded":["id"]
"editexclouded":["id"],
"subtables":[
{
"field":"upappid",
"subtable": "upappapis",
"title": "API集"
},
{
"field":"upappid",
"subtable": "upappkey",
"title": "APIKEY"
}
]
}
}

View File

@ -8,6 +8,6 @@
"exclouded":["id"],
"alters":{}
},
"editexclouded":["id"]
"editexclouded":["id", "upappid"]
}
}

View File

@ -2,6 +2,7 @@
"tblname":"upappkey",
"params":{
"title":"上位系统密码",
"logined_userorgid":"ownerid",
"description":"上位系统密码",
"sortby":"name",
"confidential_fields":["akikey", "userpwd" ],
@ -9,6 +10,6 @@
"exclouded":["id"],
"alters":{}
},
"editexclouded":["id"]
"editexclouded":["id", "upappid", "ownerid"]
}
}

Binary file not shown.

Binary file not shown.

View File

@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
from uapi.version import __version__
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
required = []
with open('requirements.txt', 'r') as f:
ls = f.read()
required = ls.split('\n')
with open('uapi/version.py', 'r') as f:
x = f.read()
y = x[x.index("'")+1:]
z = y[:y.index("'")]
version = z
with open("README.md", "r") as fh:
long_description = fh.read()
name = "uapi"
description = "uapi"
author = "yumoqing"
email = "yumoqing@gmail.com"
package_data = {}
setup(
name="uapi",
version=version,
# uncomment the following lines if you fill them out in release.py
description=description,
author=author,
author_email=email,
platforms='any',
install_requires=required ,
packages=[
"uapi"
],
package_data=package_data,
keywords = [
],
url="https://github.com/yumoqing/uapi",
long_description=long_description,
long_description_content_type="text/markdown",
classifiers = [
'Operating System :: OS Independent',
'Programming Language :: Python :: 3',
'License :: OSI Approved :: MIT License',
],
)

View File

@ -1 +0,0 @@
from .version import __version__

View File

@ -1,17 +1,174 @@
import json
from traceback import format_exc
from functools import partial
from sqlor.dbpools import DBPools
from appPublic.httpclient import JsonHttpAPI
from appPublic.log import debug, exception,
from appPublic.streamhttpclient import StreamHttpClient, liner
from appPublic.log import debug, exception, error
from ahserver.globalEnv import password_decode
from aiohttp import web
class AppAPI(JsonHttpAPI):
def get_dbname():
f = get_serverenv('get_module_dbname')
dbname = f('uapi')
return dbname
class UAPI(StreamHttpClient):
def __init__(self, appid, env={}):
self.appid = appid
super().__init__(env=env)
super().__init__()
self.te = MyTemplateEngine([], env=env)
self.env = env
self.auth_api = None
self.auth_ret = None
async def tmplrender(self, tmplstr, params={}):
if tmplstr is None:
return None
ns = self.env.copy()
ns.update(params)
te = MyTemplateEngine([], env=self.env)
return te.renders(headerstmpl, ns)
async def make_call(self, request, upapiid, callerid,
params={},
stream=False,
env={}):
self.env.update(env)
db = DBPools()
dbname = get_dbname()
uapi = None
auth_uapi = None
async with db.sqlorContext(dbname) as sor:
upapi = await self.get_upappapis(upapiid)
kinfo = await self.get_orgapikey(sor, upapi.upappid, callerid)
self.env.update(kinfo)
auth_uapi = None
if kinfo.auth_apiid:
auth_upapi = await self.get_upappapis(kinfo.auth_apiid)
auth_uapi = await self.get_uapi(sor, kinfo.auth_upapi.uapiid)
uapi = await self.get_uapi(sor, upapi.uapiid)
if auth_uapi:
await self.do_auth(auth_uapi)
if stream:
return await self.do_stream_call(self, request, uapi, params=params)
else:
return await self.do_call(self, request, uapi, params=params)
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, request, api, params={}):
url = self.env.get('baseurl') + api.path
method = auth_uapi.httpmethod
header = self.rendertmpl(api.headers, params=params)
header = json.loads(headers)
body = self.rendertmpl(api.body, params=params)
_params = self.rendertmpl(api.params, params=params)
if _params:
_params = json.loads(_params)
b = await self.request(method, url,
headers=headers,
body=body,
params=_params)
d = json.loads(b.encode('utf-8'))
if api.response:
return self.rendertmpl(api.response, params=d)
return d
async def do_stream_call(self, request, api, params={}):
self.resp = web.StreamResponse()
await self.resp.prepare(request)
url = self.env.get('baseurl') + api.path
method = auth_uapi.httpmethod
header = self.rendertmpl(api.headers, params=params)
header = json.loads(headers)
body = self.rendertmpl(api.body, params=params)
if body:
bdy = json.loads(body)
bdy['stream'] = True
body = bdy.dumps(bdy, ensure_ascii=False)
_params = self.rendertmpl(api.params, params=params)
if _params:
_params = json.loads(_params)
hc = StreamHttpClient()
gen = hc(method, url,
headers=headers,
body=body,
params=_params)
self.chunk_match = self.api.chunk_match or ''
async for line in liner(gen):
self.streamline_handle(line)
return self.resp
async def streamline_handle(self, line, resptmpl):
cnt = len(self.chunk_match)
line = line[cnt:]
try:
dic = json.loads(line)
if resptmpl:
jstr = self.rendertmpl(resptmpl, dic) + '\n'
else:
jstr = json.dumps(self.resp_json, ensure_ascii=False) + '\n'
bin = jstr.encode('utf-8')
await self.resp.write(bin)
await self.resp.drain()
except Exception as e:
exception(f'{line=}\n{e=},{format_exc()}')
return
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 {
'apikey':r.apikey,
'secretkey':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})
@ -59,80 +216,3 @@ where
return recs[0]
return None
async def request(self, apiname, ns):
orgs = ns.get('orgs')
api = await self.get_apiinfo(orgs, apiname)
if api is None:
e = Exception(f'{self.appid} app has not {apiname} api')
exception(f'{e=}, {format_exc()}')
raise e
ns1 = self.env.copy()
ns1['apikey'] = api.apikey
ns1['secretkey'] = api.secretkey
ns1.update(ns)
if api.need_auth:
if self.auth_ret is None:
auth_url = api.baseurl + self.auth_api.path
self.auth_ret = await self.call(auth_url,
method=self.auth_api.httpmethod,
ns=ns1,
headerstmpl=self.auth_api.headers,
paramstmpl=self.auth_api.params,
datatmpl=self.auth_api.data,
resptmpl=self.auth_api.response)
if self.auth_ret is None:
e = Exception(f'{self.appid} app auth error')
exception(f'{e=}, {format_exc()}')
raise e
ns1.update(self.auth_ret)
stream_func = None
if api.stream:
await self.stream_begin()
stream_func = partial(self.stream_func, api, ns1)
url = api.baseurl + api.path
ret = await self.call(url, method=api.httpmethod, ns=ns1,
stream_func = stream_func,
headerstmpl=api.headers,
paramstmpl=api.params,
datatmpl=api.data,
resptmpl=api.response)
if api.stream:
return await self.stream_end()
return ret
async def stream_begin(self):
pass
async def stream_end(self):
return None
async def stream_func(self, api, ns, chunk):
if api.chunk_match:
chunk = chunk[len(api.chunk_match):]
d = chunk
if api.streamresponse:
d = json.loads(chunk)
ns1 = ns.copy()
ns1.update(d)
d = self.te.renders(api.streamresponse, ns1)
return d
class AiohttpAppAPI(AppAPI):
async def request(self, request, apiname, ns):
self.request = request
return await super().request(apiname, ns)
async def stream_begin(self):
self.resp = web.StreamResponse()
await self.resp.prepare(request)
async def stream_end(self):
return self.resp
async def stream_func(self, api, ns, chunk):
d = super().stream_func(api, ns, chuck)
b = d.encode('utf-8')
await self.resp.write(bin)
await self.resp.drain()

View File

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

View File

@ -1 +0,0 @@
__version__ = '0.0.1'