diff --git a/json/uapi.json b/json/uapi.json new file mode 100644 index 0000000..9641754 --- /dev/null +++ b/json/uapi.json @@ -0,0 +1,13 @@ +{ + "tblname":"uapi", + "params":{ + "title":"API", + "description":"API定义", + "sortby":"name", + "browserfields":{ + "exclouded":["id"], + "alters":{} + }, + "editexclouded":["id", "usid"] + } +} diff --git a/json/uapiset.json b/json/uapiset.json index 37f80de..00ded72 100644 --- a/json/uapiset.json +++ b/json/uapiset.json @@ -8,6 +8,13 @@ "exclouded":["id"], "alters":{} }, - "editexclouded":["id"] + "editexclouded":["id"], + "subtables":[ + { + "field":"uapiid", + "subtable": "uapi", + "title": "API" + } + ] } } diff --git a/json/upapp.json b/json/upapp.json index 6df732c..839383f 100644 --- a/json/upapp.json +++ b/json/upapp.json @@ -8,6 +8,18 @@ "exclouded":["id"], "alters":{} }, - "editexclouded":["id"] + "editexclouded":["id"], + "subtables":[ + { + "field":"upappid", + "subtable": "upappapis", + "title": "API集" + }, + { + "field":"upappid", + "subtable": "upappkey", + "title": "APIKEY" + } + ] } } diff --git a/json/upappapis.json b/json/upappapis.json index 456919f..7e21308 100644 --- a/json/upappapis.json +++ b/json/upappapis.json @@ -8,6 +8,6 @@ "exclouded":["id"], "alters":{} }, - "editexclouded":["id"] + "editexclouded":["id", "upappid"] } } diff --git a/json/upappkey.json b/json/upappkey.json index 1397974..3990028 100644 --- a/json/upappkey.json +++ b/json/upappkey.json @@ -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"] } } diff --git a/models/uapi.xlsx b/models/uapi.xlsx index 16f4296..8ec69c0 100644 Binary files a/models/uapi.xlsx and b/models/uapi.xlsx differ diff --git a/models/upapp.xlsx b/models/upapp.xlsx index 64ed3aa..858f1c5 100644 Binary files a/models/upapp.xlsx and b/models/upapp.xlsx differ diff --git a/setup.py b/setup.py deleted file mode 100755 index 939784a..0000000 --- a/setup.py +++ /dev/null @@ -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', - ], -) diff --git a/uapi/__init__.py b/uapi/__init__.py index 58f3ace..e69de29 100644 --- a/uapi/__init__.py +++ b/uapi/__init__.py @@ -1 +0,0 @@ -from .version import __version__ diff --git a/uapi/appapi.py b/uapi/appapi.py index a342375..5d97f12 100644 --- a/uapi/appapi.py +++ b/uapi/appapi.py @@ -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() - diff --git a/uapi/init.py b/uapi/init.py index 0c585c4..cb41627 100644 --- a/uapi/init.py +++ b/uapi/init.py @@ -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 diff --git a/uapi/version.py b/uapi/version.py deleted file mode 100644 index b8023d8..0000000 --- a/uapi/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '0.0.1'