bella-openapi 1.0.2__py3-none-any.whl → 1.0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bella_openapi/__init__.py +22 -22
- bella_openapi/auth_billing.py +91 -91
- bella_openapi/authorize.py +61 -61
- bella_openapi/bella_trace/__init__.py +13 -13
- bella_openapi/bella_trace/_context.py +61 -61
- bella_openapi/bella_trace/fastapi_interceptor.py +28 -28
- bella_openapi/bella_trace/record_log.py +58 -58
- bella_openapi/bella_trace/trace_requests.py +58 -58
- bella_openapi/config.py +15 -15
- bella_openapi/console/__init__.py +2 -2
- bella_openapi/console/models.py +44 -44
- bella_openapi/exception.py +7 -7
- bella_openapi/log.py +222 -222
- bella_openapi/middleware/__init__.py +3 -3
- bella_openapi/middleware/context_middleware.py +108 -108
- bella_openapi/openapi_contexvar.py +6 -6
- bella_openapi/schema.py +63 -63
- {bella_openapi-1.0.2.dist-info → bella_openapi-1.0.2.1.dist-info}/METADATA +260 -260
- bella_openapi-1.0.2.1.dist-info/RECORD +22 -0
- {bella_openapi-1.0.2.dist-info → bella_openapi-1.0.2.1.dist-info}/licenses/LICENSE +20 -20
- bella_openapi-1.0.2.dist-info/RECORD +0 -22
- {bella_openapi-1.0.2.dist-info → bella_openapi-1.0.2.1.dist-info}/WHEEL +0 -0
- {bella_openapi-1.0.2.dist-info → bella_openapi-1.0.2.1.dist-info}/top_level.txt +0 -0
@@ -1,108 +1,108 @@
|
|
1
|
-
import uuid
|
2
|
-
|
3
|
-
import werkzeug
|
4
|
-
from werkzeug.routing import Map, Rule
|
5
|
-
from starlette.datastructures import URL
|
6
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
7
|
-
from starlette.responses import JSONResponse
|
8
|
-
|
9
|
-
from bella_openapi import caller_id_context, request_url_context, trace_id_context
|
10
|
-
from bella_openapi import validate_token
|
11
|
-
from bella_openapi.exception import AuthorizationException
|
12
|
-
from urllib.parse import parse_qs
|
13
|
-
|
14
|
-
|
15
|
-
class WebSocketHttpContextMiddleware:
|
16
|
-
def __init__(self, app, *, exclude_url: list[str] = None):
|
17
|
-
self.app = app
|
18
|
-
self.exclude_url = exclude_url or []
|
19
|
-
|
20
|
-
async def __call__(self, scope, receive, send):
|
21
|
-
if scope["type"] != "websocket":
|
22
|
-
return await self.app(scope, receive, send)
|
23
|
-
|
24
|
-
url = URL(scope=scope)
|
25
|
-
if match_url(self.exclude_url, url.path):
|
26
|
-
return await self.app(scope, receive, send)
|
27
|
-
|
28
|
-
query_params = parse_qs(url.query)
|
29
|
-
if not (query_params.get('token')) or query_params.get('token') == '':
|
30
|
-
# send token required error
|
31
|
-
await send({
|
32
|
-
"type": "websocket.close",
|
33
|
-
"code": 1006,
|
34
|
-
"reason": "token required",
|
35
|
-
})
|
36
|
-
return
|
37
|
-
try:
|
38
|
-
token = query_params.get('token')
|
39
|
-
caller = validate_token(token[0])
|
40
|
-
except AuthorizationException:
|
41
|
-
await send({
|
42
|
-
"type": "websocket.close",
|
43
|
-
"code": 1006,
|
44
|
-
"reason": "token invalid",
|
45
|
-
})
|
46
|
-
return
|
47
|
-
else:
|
48
|
-
caller_context_token = caller_id_context.set(caller)
|
49
|
-
trace_id_context_token = trace_id_context.set(str(uuid.uuid4()))
|
50
|
-
request_url_context_token = request_url_context.set(url.path)
|
51
|
-
await self.app(scope, receive, send)
|
52
|
-
caller_id_context.reset(caller_context_token)
|
53
|
-
trace_id_context.reset(trace_id_context_token)
|
54
|
-
request_url_context.reset(request_url_context_token)
|
55
|
-
|
56
|
-
|
57
|
-
def match_url(patterns, url):
|
58
|
-
if patterns is None:
|
59
|
-
return False
|
60
|
-
# 创建 URL 规则
|
61
|
-
rules = [Rule(pattern) for pattern in patterns]
|
62
|
-
# 匹配 URL
|
63
|
-
adapter = Map(rules).bind('')
|
64
|
-
try:
|
65
|
-
adapter.match(url)
|
66
|
-
return True
|
67
|
-
except werkzeug.exceptions.NotFound:
|
68
|
-
return False
|
69
|
-
|
70
|
-
|
71
|
-
class HttpContextMiddleware(BaseHTTPMiddleware):
|
72
|
-
def __init__(self, app, *, exclude_url: list[str] = None, ):
|
73
|
-
"""
|
74
|
-
:param app
|
75
|
-
:param exclude_url: 不需要验证token的url,
|
76
|
-
根据https://werkzeug.palletsprojects.com/en/2.2.x/routing/ 规则进行配置
|
77
|
-
"""
|
78
|
-
super().__init__(app)
|
79
|
-
self.exclude_url = exclude_url
|
80
|
-
|
81
|
-
async def dispatch(self, request, call_next):
|
82
|
-
if match_url(self.exclude_url, request.url.path):
|
83
|
-
return await call_next(request)
|
84
|
-
|
85
|
-
if request.url.path.startswith("/v1/actuator/health"):
|
86
|
-
return await call_next(request)
|
87
|
-
authorization = request.headers.get("Authorization")
|
88
|
-
if authorization is None:
|
89
|
-
return JSONResponse(status_code=401, content={"message": "empty Authorization header"})
|
90
|
-
|
91
|
-
try:
|
92
|
-
caller = validate_token(authorization)
|
93
|
-
except AuthorizationException as e:
|
94
|
-
return JSONResponse(status_code=401, content={"message": e.message})
|
95
|
-
|
96
|
-
caller_context_token = caller_id_context.set(caller)
|
97
|
-
trace_id_context_token = trace_id_context.set(str(uuid.uuid4()))
|
98
|
-
request_url_context_token = request_url_context.set(request.url.path)
|
99
|
-
|
100
|
-
# 继续处理请求
|
101
|
-
response = await call_next(request)
|
102
|
-
|
103
|
-
# 重置contextvars上下文
|
104
|
-
caller_id_context.reset(caller_context_token)
|
105
|
-
trace_id_context.reset(trace_id_context_token)
|
106
|
-
request_url_context.reset(request_url_context_token)
|
107
|
-
|
108
|
-
return response
|
1
|
+
import uuid
|
2
|
+
|
3
|
+
import werkzeug
|
4
|
+
from werkzeug.routing import Map, Rule
|
5
|
+
from starlette.datastructures import URL
|
6
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
7
|
+
from starlette.responses import JSONResponse
|
8
|
+
|
9
|
+
from bella_openapi import caller_id_context, request_url_context, trace_id_context
|
10
|
+
from bella_openapi import validate_token
|
11
|
+
from bella_openapi.exception import AuthorizationException
|
12
|
+
from urllib.parse import parse_qs
|
13
|
+
|
14
|
+
|
15
|
+
class WebSocketHttpContextMiddleware:
|
16
|
+
def __init__(self, app, *, exclude_url: list[str] = None):
|
17
|
+
self.app = app
|
18
|
+
self.exclude_url = exclude_url or []
|
19
|
+
|
20
|
+
async def __call__(self, scope, receive, send):
|
21
|
+
if scope["type"] != "websocket":
|
22
|
+
return await self.app(scope, receive, send)
|
23
|
+
|
24
|
+
url = URL(scope=scope)
|
25
|
+
if match_url(self.exclude_url, url.path):
|
26
|
+
return await self.app(scope, receive, send)
|
27
|
+
|
28
|
+
query_params = parse_qs(url.query)
|
29
|
+
if not (query_params.get('token')) or query_params.get('token') == '':
|
30
|
+
# send token required error
|
31
|
+
await send({
|
32
|
+
"type": "websocket.close",
|
33
|
+
"code": 1006,
|
34
|
+
"reason": "token required",
|
35
|
+
})
|
36
|
+
return
|
37
|
+
try:
|
38
|
+
token = query_params.get('token')
|
39
|
+
caller = validate_token(token[0])
|
40
|
+
except AuthorizationException:
|
41
|
+
await send({
|
42
|
+
"type": "websocket.close",
|
43
|
+
"code": 1006,
|
44
|
+
"reason": "token invalid",
|
45
|
+
})
|
46
|
+
return
|
47
|
+
else:
|
48
|
+
caller_context_token = caller_id_context.set(caller)
|
49
|
+
trace_id_context_token = trace_id_context.set(str(uuid.uuid4()))
|
50
|
+
request_url_context_token = request_url_context.set(url.path)
|
51
|
+
await self.app(scope, receive, send)
|
52
|
+
caller_id_context.reset(caller_context_token)
|
53
|
+
trace_id_context.reset(trace_id_context_token)
|
54
|
+
request_url_context.reset(request_url_context_token)
|
55
|
+
|
56
|
+
|
57
|
+
def match_url(patterns, url):
|
58
|
+
if patterns is None:
|
59
|
+
return False
|
60
|
+
# 创建 URL 规则
|
61
|
+
rules = [Rule(pattern) for pattern in patterns]
|
62
|
+
# 匹配 URL
|
63
|
+
adapter = Map(rules).bind('')
|
64
|
+
try:
|
65
|
+
adapter.match(url)
|
66
|
+
return True
|
67
|
+
except werkzeug.exceptions.NotFound:
|
68
|
+
return False
|
69
|
+
|
70
|
+
|
71
|
+
class HttpContextMiddleware(BaseHTTPMiddleware):
|
72
|
+
def __init__(self, app, *, exclude_url: list[str] = None, ):
|
73
|
+
"""
|
74
|
+
:param app
|
75
|
+
:param exclude_url: 不需要验证token的url,
|
76
|
+
根据https://werkzeug.palletsprojects.com/en/2.2.x/routing/ 规则进行配置
|
77
|
+
"""
|
78
|
+
super().__init__(app)
|
79
|
+
self.exclude_url = exclude_url
|
80
|
+
|
81
|
+
async def dispatch(self, request, call_next):
|
82
|
+
if match_url(self.exclude_url, request.url.path):
|
83
|
+
return await call_next(request)
|
84
|
+
|
85
|
+
if request.url.path.startswith("/v1/actuator/health"):
|
86
|
+
return await call_next(request)
|
87
|
+
authorization = request.headers.get("Authorization")
|
88
|
+
if authorization is None:
|
89
|
+
return JSONResponse(status_code=401, content={"message": "empty Authorization header"})
|
90
|
+
|
91
|
+
try:
|
92
|
+
caller = validate_token(authorization)
|
93
|
+
except AuthorizationException as e:
|
94
|
+
return JSONResponse(status_code=401, content={"message": e.message})
|
95
|
+
|
96
|
+
caller_context_token = caller_id_context.set(caller)
|
97
|
+
trace_id_context_token = trace_id_context.set(str(uuid.uuid4()))
|
98
|
+
request_url_context_token = request_url_context.set(request.url.path)
|
99
|
+
|
100
|
+
# 继续处理请求
|
101
|
+
response = await call_next(request)
|
102
|
+
|
103
|
+
# 重置contextvars上下文
|
104
|
+
caller_id_context.reset(caller_context_token)
|
105
|
+
trace_id_context.reset(trace_id_context_token)
|
106
|
+
request_url_context.reset(request_url_context_token)
|
107
|
+
|
108
|
+
return response
|
@@ -1,6 +1,6 @@
|
|
1
|
-
from contextvars import ContextVar
|
2
|
-
from typing import Optional
|
3
|
-
|
4
|
-
trace_id_context: ContextVar[Optional[str]] = ContextVar("trace_id", default=None)
|
5
|
-
caller_id_context: ContextVar[Optional[str]] = ContextVar("caller_id", default=None)
|
6
|
-
request_url_context: ContextVar[Optional[str]] = ContextVar("request_url", default=None)
|
1
|
+
from contextvars import ContextVar
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
trace_id_context: ContextVar[Optional[str]] = ContextVar("trace_id", default=None)
|
5
|
+
caller_id_context: ContextVar[Optional[str]] = ContextVar("caller_id", default=None)
|
6
|
+
request_url_context: ContextVar[Optional[str]] = ContextVar("request_url", default=None)
|
bella_openapi/schema.py
CHANGED
@@ -1,63 +1,63 @@
|
|
1
|
-
from datetime import datetime
|
2
|
-
from typing import Optional
|
3
|
-
|
4
|
-
import uuid
|
5
|
-
|
6
|
-
import pydantic
|
7
|
-
from pydantic import BaseModel, Field
|
8
|
-
from .openapi_contexvar import trace_id_context, caller_id_context, request_url_context
|
9
|
-
|
10
|
-
|
11
|
-
class BaseOperationLog(BaseModel):
|
12
|
-
uuid: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
13
|
-
request_id: Optional[str] = Field(default_factory=lambda: trace_id_context.get(),
|
14
|
-
alias='requestId')
|
15
|
-
caller_id: Optional[str] = Field(default_factory=lambda: caller_id_context.get(),
|
16
|
-
alias='callerId')
|
17
|
-
request_url: Optional[str] = Field(default_factory=lambda: request_url_context.get(),
|
18
|
-
alias='requestUrl')
|
19
|
-
op_log_type: str = Field(alias='opLogType')
|
20
|
-
op_type: str = Field(alias='opType')
|
21
|
-
is_cost_log: bool = Field(default=False, alias='isCostLog')
|
22
|
-
operation_status: str = Field(alias='operationStatus')
|
23
|
-
start_time_millis: int = Field(default_factory=lambda: int(datetime.now().timestamp() * 1000),
|
24
|
-
alias='startTimeMillis')
|
25
|
-
duration_millis: int = Field(default=0, alias='durationMillis')
|
26
|
-
request: object
|
27
|
-
response: object = Field(default=None)
|
28
|
-
err_msg: Optional[str] = Field(default=None, alias='errMsg')
|
29
|
-
extra_info: dict = Field(default={}, alias='extraInfo')
|
30
|
-
ucid: str = Field(default='ucid')
|
31
|
-
|
32
|
-
@staticmethod
|
33
|
-
def validate(values):
|
34
|
-
if 'request_id' not in values or values['request_id'] is None:
|
35
|
-
raise ValueError('request_id is required, please set trace_id_context')
|
36
|
-
if 'caller_id' not in values or values['caller_id'] is None:
|
37
|
-
raise ValueError('caller_id is required, please set caller_id_context')
|
38
|
-
if 'request_url' not in values or values['request_url'] is None:
|
39
|
-
raise ValueError('request_url is required, please set request_url_context')
|
40
|
-
return values
|
41
|
-
|
42
|
-
|
43
|
-
if pydantic.version.VERSION.startswith('1.'):
|
44
|
-
from pydantic import root_validator
|
45
|
-
|
46
|
-
|
47
|
-
class OperationLog(BaseOperationLog):
|
48
|
-
@root_validator
|
49
|
-
def validate_duration_millis(cls, values):
|
50
|
-
super().validate(values)
|
51
|
-
return values
|
52
|
-
|
53
|
-
|
54
|
-
else:
|
55
|
-
from pydantic import model_validator
|
56
|
-
|
57
|
-
|
58
|
-
class OperationLog(BaseOperationLog):
|
59
|
-
|
60
|
-
@model_validator(mode='after')
|
61
|
-
def validate_duration_millis(cls, values):
|
62
|
-
super().validate(values.dict())
|
63
|
-
return values
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
import uuid
|
5
|
+
|
6
|
+
import pydantic
|
7
|
+
from pydantic import BaseModel, Field
|
8
|
+
from .openapi_contexvar import trace_id_context, caller_id_context, request_url_context
|
9
|
+
|
10
|
+
|
11
|
+
class BaseOperationLog(BaseModel):
|
12
|
+
uuid: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
13
|
+
request_id: Optional[str] = Field(default_factory=lambda: trace_id_context.get(),
|
14
|
+
alias='requestId')
|
15
|
+
caller_id: Optional[str] = Field(default_factory=lambda: caller_id_context.get(),
|
16
|
+
alias='callerId')
|
17
|
+
request_url: Optional[str] = Field(default_factory=lambda: request_url_context.get(),
|
18
|
+
alias='requestUrl')
|
19
|
+
op_log_type: str = Field(alias='opLogType')
|
20
|
+
op_type: str = Field(alias='opType')
|
21
|
+
is_cost_log: bool = Field(default=False, alias='isCostLog')
|
22
|
+
operation_status: str = Field(alias='operationStatus')
|
23
|
+
start_time_millis: int = Field(default_factory=lambda: int(datetime.now().timestamp() * 1000),
|
24
|
+
alias='startTimeMillis')
|
25
|
+
duration_millis: int = Field(default=0, alias='durationMillis')
|
26
|
+
request: object
|
27
|
+
response: object = Field(default=None)
|
28
|
+
err_msg: Optional[str] = Field(default=None, alias='errMsg')
|
29
|
+
extra_info: dict = Field(default={}, alias='extraInfo')
|
30
|
+
ucid: str = Field(default='ucid')
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
def validate(values):
|
34
|
+
if 'request_id' not in values or values['request_id'] is None:
|
35
|
+
raise ValueError('request_id is required, please set trace_id_context')
|
36
|
+
if 'caller_id' not in values or values['caller_id'] is None:
|
37
|
+
raise ValueError('caller_id is required, please set caller_id_context')
|
38
|
+
if 'request_url' not in values or values['request_url'] is None:
|
39
|
+
raise ValueError('request_url is required, please set request_url_context')
|
40
|
+
return values
|
41
|
+
|
42
|
+
|
43
|
+
if pydantic.version.VERSION.startswith('1.'):
|
44
|
+
from pydantic import root_validator
|
45
|
+
|
46
|
+
|
47
|
+
class OperationLog(BaseOperationLog):
|
48
|
+
@root_validator
|
49
|
+
def validate_duration_millis(cls, values):
|
50
|
+
super().validate(values)
|
51
|
+
return values
|
52
|
+
|
53
|
+
|
54
|
+
else:
|
55
|
+
from pydantic import model_validator
|
56
|
+
|
57
|
+
|
58
|
+
class OperationLog(BaseOperationLog):
|
59
|
+
|
60
|
+
@model_validator(mode='after')
|
61
|
+
def validate_duration_millis(cls, values):
|
62
|
+
super().validate(values.dict())
|
63
|
+
return values
|