polaris-python 0.0.1.dev3__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.
- polaris/__init__.py +74 -0
- polaris/_version.py +16 -0
- polaris/api/__init__.py +55 -0
- polaris/api/_log_file.py +232 -0
- polaris/api/config.py +145 -0
- polaris/api/consumer.py +240 -0
- polaris/api/log.py +414 -0
- polaris/api/lossless.py +73 -0
- polaris/api/provider.py +152 -0
- polaris/api/skill.py +162 -0
- polaris/pkg/__init__.py +15 -0
- polaris/pkg/common/__init__.py +15 -0
- polaris/pkg/common/plugin.py +33 -0
- polaris/pkg/config/__init__.py +15 -0
- polaris/pkg/config/api.py +1205 -0
- polaris/pkg/config/default.py +92 -0
- polaris/pkg/config/event.py +76 -0
- polaris/pkg/config/loader.py +392 -0
- polaris/pkg/config/stat_reporter.py +100 -0
- polaris/pkg/flow/__init__.py +15 -0
- polaris/pkg/flow/admin_server.py +104 -0
- polaris/pkg/flow/async_flow.py +55 -0
- polaris/pkg/flow/base_flow.py +111 -0
- polaris/pkg/flow/impl.py +1277 -0
- polaris/pkg/flow/lossless.py +605 -0
- polaris/pkg/flow/notify.py +46 -0
- polaris/pkg/flow/register.py +210 -0
- polaris/pkg/flow/skill.py +110 -0
- polaris/pkg/flow/sync_flow.py +20 -0
- polaris/pkg/model/__init__.py +15 -0
- polaris/pkg/model/context.py +53 -0
- polaris/pkg/model/contract.py +89 -0
- polaris/pkg/model/core.py +51 -0
- polaris/pkg/model/engine.py +83 -0
- polaris/pkg/model/error.py +94 -0
- polaris/pkg/model/event.py +159 -0
- polaris/pkg/model/lossless.py +102 -0
- polaris/pkg/model/rule.py +152 -0
- polaris/pkg/model/server.py +288 -0
- polaris/pkg/model/service.py +820 -0
- polaris/pkg/model/skill.py +179 -0
- polaris/pkg/model/stat.py +168 -0
- polaris/pkg/model/util.py +58 -0
- polaris/pkg/version/__init__.py +18 -0
- polaris/plugin/__init__.py +32 -0
- polaris/plugin/circuit_breaker/__init__.py +19 -0
- polaris/plugin/circuit_breaker/base.py +54 -0
- polaris/plugin/circuit_breaker/composite.py +265 -0
- polaris/plugin/circuit_breaker/config.py +36 -0
- polaris/plugin/event/__init__.py +21 -0
- polaris/plugin/event/pushgateway.py +202 -0
- polaris/plugin/load_balancer/__init__.py +24 -0
- polaris/plugin/load_balancer/base.py +30 -0
- polaris/plugin/load_balancer/random.py +39 -0
- polaris/plugin/load_balancer/weighted_random.py +50 -0
- polaris/plugin/local_registry/__init__.py +18 -0
- polaris/plugin/local_registry/base.py +47 -0
- polaris/plugin/local_registry/file_cache.py +335 -0
- polaris/plugin/local_registry/local_cache.py +433 -0
- polaris/plugin/server_connector/__init__.py +20 -0
- polaris/plugin/server_connector/base.py +104 -0
- polaris/plugin/server_connector/convert.py +149 -0
- polaris/plugin/server_connector/grpc_connector/__init__.py +21 -0
- polaris/plugin/server_connector/grpc_connector/connection.py +401 -0
- polaris/plugin/server_connector/grpc_connector/grpc_client.py +384 -0
- polaris/plugin/server_connector/grpc_connector/tls.py +45 -0
- polaris/plugin/service_router/__init__.py +28 -0
- polaris/plugin/service_router/base.py +105 -0
- polaris/plugin/service_router/isolated.py +35 -0
- polaris/plugin/service_router/metadata.py +84 -0
- polaris/plugin/service_router/recover.py +62 -0
- polaris/plugin/service_router/util.py +54 -0
- polaris/plugin/skill/__init__.py +16 -0
- polaris/plugin/skill/connector.py +127 -0
- polaris/plugin/skill/persistence.py +94 -0
- polaris/plugin/stat_reporter/__init__.py +16 -0
- polaris/plugin/stat_reporter/base.py +34 -0
- polaris/plugin/stat_reporter/prometheus.py +375 -0
- polaris/plugin_manager.py +146 -0
- polaris_python-0.0.1.dev3.dist-info/METADATA +213 -0
- polaris_python-0.0.1.dev3.dist-info/RECORD +84 -0
- polaris_python-0.0.1.dev3.dist-info/WHEEL +5 -0
- polaris_python-0.0.1.dev3.dist-info/licenses/LICENSE +18 -0
- polaris_python-0.0.1.dev3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""Instance heartbeat scheduling and Java-compatible re-registration."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
import copy
|
|
22
|
+
import random
|
|
23
|
+
from collections.abc import Awaitable, Callable
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from weakref import WeakValueDictionary
|
|
26
|
+
|
|
27
|
+
import polaris.api.log as logger
|
|
28
|
+
from polaris.pkg.model.error import SDKError, ServerCode
|
|
29
|
+
from polaris.pkg.model.server import (
|
|
30
|
+
InstanceDeRegisterRequest,
|
|
31
|
+
InstanceHeartbeatRequest,
|
|
32
|
+
InstanceRegisterRequest,
|
|
33
|
+
InstanceRegisterResponse,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
InstanceKey = tuple[str, str, str, int]
|
|
37
|
+
base_logger = logger.get_logger("base")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _instance_key(request: InstanceRegisterRequest | InstanceDeRegisterRequest) -> InstanceKey:
|
|
41
|
+
return request.namespace, request.service, request.host, request.port
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class RegisterState:
|
|
46
|
+
"""Keep a private request snapshot and the tasks for one registered instance."""
|
|
47
|
+
|
|
48
|
+
request: InstanceRegisterRequest
|
|
49
|
+
first_register_time: float
|
|
50
|
+
heartbeat_task: asyncio.Task[None] | None = None
|
|
51
|
+
re_register_task: asyncio.Task[None] | None = None
|
|
52
|
+
heartbeat_fail_count: int = 0
|
|
53
|
+
re_register_count: int = 0
|
|
54
|
+
instance_ids: set[str] = field(default_factory=set)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RegisterStateManager:
|
|
58
|
+
"""Own registration tasks on the SDK loop and cancel them before deregistration."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
register: Callable[[InstanceRegisterRequest], Awaitable[InstanceRegisterResponse]],
|
|
63
|
+
heartbeat: Callable[[InstanceHeartbeatRequest], Awaitable[None]],
|
|
64
|
+
min_register_interval: int,
|
|
65
|
+
) -> None:
|
|
66
|
+
self._register = register
|
|
67
|
+
self._heartbeat = heartbeat
|
|
68
|
+
self._min_register_interval = min_register_interval / 1000
|
|
69
|
+
self._states: dict[InstanceKey, RegisterState] = {}
|
|
70
|
+
self._locks: WeakValueDictionary[InstanceKey, asyncio.Lock] = WeakValueDictionary()
|
|
71
|
+
self._closed = False
|
|
72
|
+
|
|
73
|
+
def _key_for(self, request: InstanceRegisterRequest | InstanceDeRegisterRequest) -> InstanceKey:
|
|
74
|
+
if request.instance_id:
|
|
75
|
+
for key, state in self._states.items():
|
|
76
|
+
if request.instance_id in state.instance_ids:
|
|
77
|
+
return key
|
|
78
|
+
return _instance_key(request)
|
|
79
|
+
|
|
80
|
+
def operation_lock(self, request: InstanceRegisterRequest | InstanceDeRegisterRequest) -> asyncio.Lock:
|
|
81
|
+
"""Serialize explicit registration and deregistration for the same instance."""
|
|
82
|
+
key = self._key_for(request)
|
|
83
|
+
lock = self._locks.get(key)
|
|
84
|
+
if lock is None:
|
|
85
|
+
lock = asyncio.Lock()
|
|
86
|
+
self._locks[key] = lock
|
|
87
|
+
return lock
|
|
88
|
+
|
|
89
|
+
async def put(self, request: InstanceRegisterRequest) -> None:
|
|
90
|
+
"""Replace an instance's schedule without creating duplicate heartbeat tasks."""
|
|
91
|
+
previous = self._states.pop(_instance_key(request), None)
|
|
92
|
+
if previous is not None:
|
|
93
|
+
await self._cancel([previous])
|
|
94
|
+
if self._closed:
|
|
95
|
+
return
|
|
96
|
+
state = RegisterState(copy.deepcopy(request), asyncio.get_running_loop().time())
|
|
97
|
+
state.instance_ids.add(request.instance_id)
|
|
98
|
+
self._states[_instance_key(request)] = state
|
|
99
|
+
state.heartbeat_task = asyncio.create_task(self._run_heartbeat(state))
|
|
100
|
+
base_logger.info(
|
|
101
|
+
"instance heartbeat task started namespace=%s service=%s host=%s port=%s ttl=%ss",
|
|
102
|
+
request.namespace, request.service, request.host, request.port, request.ttl,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
async def remove(
|
|
106
|
+
self, request: InstanceRegisterRequest | InstanceDeRegisterRequest,
|
|
107
|
+
) -> InstanceRegisterRequest | None:
|
|
108
|
+
"""Cancel heartbeats and pending re-registration by instance ID or address."""
|
|
109
|
+
key = self._key_for(request)
|
|
110
|
+
state = self._states.get(key)
|
|
111
|
+
if state is None:
|
|
112
|
+
return
|
|
113
|
+
# An explicit ID takes precedence over possibly stale address fields.
|
|
114
|
+
if request.instance_id and request.instance_id not in state.instance_ids:
|
|
115
|
+
return
|
|
116
|
+
self._states.pop(key)
|
|
117
|
+
await self._cancel([state])
|
|
118
|
+
return state.request
|
|
119
|
+
|
|
120
|
+
async def close(self) -> None:
|
|
121
|
+
"""Stop and await all tasks before the SDK closes its connector."""
|
|
122
|
+
self._closed = True
|
|
123
|
+
states = list(self._states.values())
|
|
124
|
+
self._states.clear()
|
|
125
|
+
await self._cancel(states)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
async def _cancel(states: list[RegisterState]) -> None:
|
|
129
|
+
tasks = [task for state in states for task in (state.heartbeat_task, state.re_register_task)
|
|
130
|
+
if task is not None]
|
|
131
|
+
for state in states:
|
|
132
|
+
if state.heartbeat_task is None:
|
|
133
|
+
continue
|
|
134
|
+
request = state.request
|
|
135
|
+
base_logger.info(
|
|
136
|
+
"instance heartbeat task stopped namespace=%s service=%s host=%s port=%s",
|
|
137
|
+
request.namespace, request.service, request.host, request.port,
|
|
138
|
+
)
|
|
139
|
+
for task in tasks:
|
|
140
|
+
task.cancel()
|
|
141
|
+
if tasks:
|
|
142
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
143
|
+
|
|
144
|
+
def _active(self, state: RegisterState) -> bool:
|
|
145
|
+
return not self._closed and self._states.get(_instance_key(state.request)) is state
|
|
146
|
+
|
|
147
|
+
async def _run_heartbeat(self, state: RegisterState) -> None:
|
|
148
|
+
loop = asyncio.get_running_loop()
|
|
149
|
+
interval = state.request.ttl
|
|
150
|
+
next_tick = loop.time() + interval
|
|
151
|
+
while self._active(state):
|
|
152
|
+
await asyncio.sleep(max(0, next_tick - loop.time()))
|
|
153
|
+
if not self._active(state):
|
|
154
|
+
return
|
|
155
|
+
await self._heartbeat_once(state)
|
|
156
|
+
next_tick += interval
|
|
157
|
+
# Skip missed ticks; do not overlap RPCs or burst after a slow request.
|
|
158
|
+
if next_tick < loop.time():
|
|
159
|
+
next_tick = loop.time() + interval
|
|
160
|
+
|
|
161
|
+
async def _heartbeat_once(self, state: RegisterState) -> None:
|
|
162
|
+
request = state.request
|
|
163
|
+
heartbeat = InstanceHeartbeatRequest(
|
|
164
|
+
request.service, request.service_token, request.namespace, request.instance_id,
|
|
165
|
+
request.host, request.port,
|
|
166
|
+
)
|
|
167
|
+
try:
|
|
168
|
+
await self._heartbeat(heartbeat)
|
|
169
|
+
state.heartbeat_fail_count = 0
|
|
170
|
+
base_logger.debug(
|
|
171
|
+
"instance heartbeat success namespace=%s service=%s host=%s port=%s",
|
|
172
|
+
request.namespace, request.service, request.host, request.port,
|
|
173
|
+
)
|
|
174
|
+
except Exception as error:
|
|
175
|
+
if isinstance(error, SDKError) and error.server_code == ServerCode.NotFoundResource:
|
|
176
|
+
state.heartbeat_fail_count += 1
|
|
177
|
+
else:
|
|
178
|
+
state.heartbeat_fail_count = 0
|
|
179
|
+
logger.error("instance heartbeat failed for %s: %s", _instance_key(request), error)
|
|
180
|
+
|
|
181
|
+
if (not self._active(state) or state.heartbeat_fail_count < 2
|
|
182
|
+
or asyncio.get_running_loop().time() - state.first_register_time < self._min_register_interval):
|
|
183
|
+
return
|
|
184
|
+
if state.re_register_task is not None and not state.re_register_task.done():
|
|
185
|
+
return
|
|
186
|
+
attempt = state.re_register_count
|
|
187
|
+
delay = 0 if attempt == 0 else min(
|
|
188
|
+
request.ttl * 2 ** min(attempt - 1, 6) + random.randrange(request.ttl), 60,
|
|
189
|
+
)
|
|
190
|
+
state.re_register_count += 1
|
|
191
|
+
state.re_register_task = asyncio.create_task(self._re_register(state, delay))
|
|
192
|
+
|
|
193
|
+
async def _re_register(self, state: RegisterState, delay: float) -> None:
|
|
194
|
+
if delay:
|
|
195
|
+
await asyncio.sleep(delay)
|
|
196
|
+
if not self._active(state):
|
|
197
|
+
return
|
|
198
|
+
try:
|
|
199
|
+
response = await self._register(state.request)
|
|
200
|
+
state.request.instance_id = response.instance_id
|
|
201
|
+
state.instance_ids.add(response.instance_id)
|
|
202
|
+
state.heartbeat_fail_count = 0
|
|
203
|
+
state.re_register_count = 0
|
|
204
|
+
base_logger.info(
|
|
205
|
+
"instance re-registered namespace=%s service=%s host=%s port=%s instanceId=%s",
|
|
206
|
+
state.request.namespace, state.request.service,
|
|
207
|
+
state.request.host, state.request.port, response.instance_id,
|
|
208
|
+
)
|
|
209
|
+
except Exception as error:
|
|
210
|
+
logger.warning("instance re-registration failed for %s: %s", _instance_key(state.request), error)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""Read-only Skill orchestration with network-error persistence fallback."""
|
|
17
|
+
|
|
18
|
+
from dataclasses import fields
|
|
19
|
+
|
|
20
|
+
from polaris.pkg.model.error import ErrorCode, SDKError, ServerCode
|
|
21
|
+
from polaris.pkg.model.skill import (
|
|
22
|
+
SkillDownloadResponse, SkillGetResponse, SkillListResponse, SkillResource,
|
|
23
|
+
SkillResourceVersion, SkillStorageInfo, SkillVersionInfo,
|
|
24
|
+
)
|
|
25
|
+
from polaris.plugin.skill.persistence import SkillPersistentHandler
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _resource(proto):
|
|
29
|
+
values = {item.name: getattr(proto, item.name) for item in fields(SkillResource)}
|
|
30
|
+
values.update(tags=list(proto.tags), examples=list(proto.examples), ext=dict(proto.ext),
|
|
31
|
+
version_info=SkillVersionInfo(proto.version_info.active_version,
|
|
32
|
+
proto.version_info.latest_version, proto.version_info.total_versions))
|
|
33
|
+
return SkillResource(**values)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _version(proto):
|
|
37
|
+
values = {item.name: getattr(proto, item.name) for item in fields(SkillResourceVersion)}
|
|
38
|
+
values["storage"] = SkillStorageInfo(proto.storage.provider, list(proto.storage.files),
|
|
39
|
+
proto.storage.scope, proto.storage.content_digest)
|
|
40
|
+
return SkillResourceVersion(**values)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SkillFlow:
|
|
44
|
+
"""Run Skill reads on a context's owner loop and persist successful responses."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, connector, config, logger):
|
|
47
|
+
self.connector = connector
|
|
48
|
+
self.config = config
|
|
49
|
+
self.logger = logger
|
|
50
|
+
self.persistence = None
|
|
51
|
+
if config.persist_enable:
|
|
52
|
+
try:
|
|
53
|
+
self.persistence = SkillPersistentHandler(config, logger)
|
|
54
|
+
except OSError as error:
|
|
55
|
+
self.logger.warning("Skill backup initialization failed; using remote reads only: %s", error)
|
|
56
|
+
|
|
57
|
+
async def _read(self, request, operation):
|
|
58
|
+
loaded = False
|
|
59
|
+
try:
|
|
60
|
+
method = self.connector.get_skill if operation == "get" else self.connector.download_skill
|
|
61
|
+
result = await method(request)
|
|
62
|
+
except SDKError as error:
|
|
63
|
+
if (error.err_code != ErrorCode.ErrCodeNetworkError or not self.config.fallback_to_local_cache
|
|
64
|
+
or self.persistence is None):
|
|
65
|
+
raise
|
|
66
|
+
result = await self.persistence.load(request, operation)
|
|
67
|
+
if result is None or result.code not in (0, ServerCode.ExecuteSuccess):
|
|
68
|
+
raise error
|
|
69
|
+
loaded = True
|
|
70
|
+
self.logger.warning("use local Skill backup for %s/%s", request.namespace, request.name)
|
|
71
|
+
if loaded:
|
|
72
|
+
version = result.version.version if operation == "get" else result.version
|
|
73
|
+
if (not version or (request.version and request.version != version)
|
|
74
|
+
or (operation == "get" and (result.resource.name != request.name
|
|
75
|
+
or result.resource.namespace != request.namespace))):
|
|
76
|
+
raise SDKError(ErrorCode.ErrCodeInvalidResponse, "Skill backup identity or version mismatch")
|
|
77
|
+
if operation == "download" and result.total_size:
|
|
78
|
+
size = len(result.zip_chunk) if request.format == "zip" else len(result.content.encode("utf-8"))
|
|
79
|
+
if size != result.total_size:
|
|
80
|
+
raise SDKError(ErrorCode.ErrCodeInvalidResponse, "Skill backup size mismatch")
|
|
81
|
+
if not loaded and result.code in (0, ServerCode.ExecuteSuccess) and self.persistence is not None:
|
|
82
|
+
try:
|
|
83
|
+
self.persistence.save_success(request, result, operation)
|
|
84
|
+
except (OSError, SDKError) as error:
|
|
85
|
+
self.logger.warning("failed to persist Skill %s/%s: %s", request.namespace, request.name, error)
|
|
86
|
+
return result, loaded
|
|
87
|
+
|
|
88
|
+
async def get_skill(self, request):
|
|
89
|
+
"""Fetch published content with network-only local fallback."""
|
|
90
|
+
response, loaded = await self._read(request, "get")
|
|
91
|
+
return SkillGetResponse(_resource(response.resource), _version(response.version), response.content,
|
|
92
|
+
loaded, response.code, response.info)
|
|
93
|
+
|
|
94
|
+
async def list_skills(self, request):
|
|
95
|
+
"""Fetch remote search results without storing or falling back to a list."""
|
|
96
|
+
response = await self.connector.list_skills(request)
|
|
97
|
+
return SkillListResponse(response.total, [_resource(item) for item in response.resources],
|
|
98
|
+
response.code, response.info)
|
|
99
|
+
|
|
100
|
+
async def download_skill(self, request):
|
|
101
|
+
"""Return one complete download with network-only local fallback."""
|
|
102
|
+
response, loaded = await self._read(request, "download")
|
|
103
|
+
return SkillDownloadResponse(response.version, response.content_type, response.content,
|
|
104
|
+
response.zip_chunk, response.total_size, response.filename, loaded,
|
|
105
|
+
response.code, response.info)
|
|
106
|
+
|
|
107
|
+
def destroy(self):
|
|
108
|
+
"""Drain pending backups when the context closes."""
|
|
109
|
+
if self.persistence is not None:
|
|
110
|
+
self.persistence.destroy()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
from polaris.pkg.flow.impl import Engine
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SyncEngine(Engine):
|
|
20
|
+
"""Legacy name for the Engine's shared synchronous and asynchronous flows."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
from threading import RLock
|
|
17
|
+
from uuid import uuid4
|
|
18
|
+
|
|
19
|
+
ContextKeyEngine = "engine"
|
|
20
|
+
ContextKeyEventCenter = "event_center"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ValueContext:
|
|
24
|
+
"""Hold identity and thread-safe values belonging to one SDK context."""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.client_id = str(uuid4())
|
|
28
|
+
self.logger = None
|
|
29
|
+
self.core_map = {}
|
|
30
|
+
self._lock = RLock()
|
|
31
|
+
|
|
32
|
+
def set_value(self, key: str, value):
|
|
33
|
+
"""Store a value without touching any other SDK context."""
|
|
34
|
+
with self._lock:
|
|
35
|
+
self.core_map[key] = value
|
|
36
|
+
|
|
37
|
+
def get_value(self, key: str):
|
|
38
|
+
"""Read a value owned by this context."""
|
|
39
|
+
with self._lock:
|
|
40
|
+
return self.core_map[key]
|
|
41
|
+
|
|
42
|
+
def get_client_id(self) -> str:
|
|
43
|
+
"""Return the stable, unique identity used in ReportClient."""
|
|
44
|
+
return self.client_id
|
|
45
|
+
|
|
46
|
+
def clear(self):
|
|
47
|
+
"""Release runtime references after context destruction."""
|
|
48
|
+
with self._lock:
|
|
49
|
+
self.core_map.clear()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def new_value_context():
|
|
53
|
+
return ValueContext()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""Framework-independent service contract models."""
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import IntEnum
|
|
20
|
+
|
|
21
|
+
from polarismesh_specification.api.v1.service_manage import contract_pb2
|
|
22
|
+
|
|
23
|
+
from polaris.pkg.model.error import ErrorCode, SDKError
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ServiceFeatureType(IntEnum):
|
|
27
|
+
"""Capability categories defined by the service contract protocol."""
|
|
28
|
+
|
|
29
|
+
MCP_TOOL = contract_pb2.Service_Feature_Type_MCP_Tool
|
|
30
|
+
MCP_RESOURCE = contract_pb2.Service_Feature_Type_MCP_Resource
|
|
31
|
+
MCP_PROMPT = contract_pb2.Service_Feature_Type_MCP_Prompt
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ServiceFeatureStatus(IntEnum):
|
|
35
|
+
"""Availability of a published service capability."""
|
|
36
|
+
|
|
37
|
+
ENABLED = contract_pb2.Service_Feature_Status_Enabled
|
|
38
|
+
DISABLED = contract_pb2.Service_Feature_Status_Disabled
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ServiceFeature:
|
|
43
|
+
"""One tool, resource or prompt in a service contract."""
|
|
44
|
+
|
|
45
|
+
type: int
|
|
46
|
+
name: str
|
|
47
|
+
description: str = ""
|
|
48
|
+
content: str = ""
|
|
49
|
+
status: int = ServiceFeatureStatus.ENABLED
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class InterfaceDescriptor:
|
|
54
|
+
"""One request or notification handler exposed by a service."""
|
|
55
|
+
|
|
56
|
+
name: str = ""
|
|
57
|
+
method: str = ""
|
|
58
|
+
path: str = ""
|
|
59
|
+
content: str = ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class GetServiceContractRequest:
|
|
64
|
+
"""Identify a service contract by namespace, service, protocol and version."""
|
|
65
|
+
|
|
66
|
+
namespace: str
|
|
67
|
+
service: str
|
|
68
|
+
protocol: str = ""
|
|
69
|
+
version: str = ""
|
|
70
|
+
timeout: int = 0
|
|
71
|
+
|
|
72
|
+
def validate(self):
|
|
73
|
+
"""Check the service identity and timeout."""
|
|
74
|
+
if not self.namespace.strip() or not self.service.strip() or self.timeout < 0:
|
|
75
|
+
raise SDKError(ErrorCode.ErrCodeAPIInvalidArgument, "contract namespace/service are required, timeout >=0")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class ServiceContract(GetServiceContractRequest):
|
|
80
|
+
"""A service contract used for reporting and retrieval."""
|
|
81
|
+
|
|
82
|
+
name: str = ""
|
|
83
|
+
revision: str = ""
|
|
84
|
+
content: str = ""
|
|
85
|
+
service_features: list[ServiceFeature] = field(default_factory=list)
|
|
86
|
+
interfaces: list[InterfaceDescriptor] = field(default_factory=list)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
ReportServiceContractRequest = ServiceContract
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
class EventType:
|
|
17
|
+
Unknown = 0
|
|
18
|
+
|
|
19
|
+
Instance = 0x2001
|
|
20
|
+
|
|
21
|
+
Routing = 0x2002
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# 服务的唯一标识key
|
|
25
|
+
class ServiceKey:
|
|
26
|
+
def __init__(self, namespace: str, service: str):
|
|
27
|
+
self.namespace: str = namespace
|
|
28
|
+
self.service: str = service
|
|
29
|
+
|
|
30
|
+
def __hash__(self):
|
|
31
|
+
return hash(self.namespace + self.service)
|
|
32
|
+
|
|
33
|
+
def get_namespace(self):
|
|
34
|
+
return self.namespace
|
|
35
|
+
|
|
36
|
+
def get_service(self):
|
|
37
|
+
return self.service
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ServiceEventKey(ServiceKey):
|
|
41
|
+
def __init__(self, namespace: str, service: str, event_type: int):
|
|
42
|
+
ServiceKey.__init__(self, namespace, service)
|
|
43
|
+
self.event_type = event_type
|
|
44
|
+
|
|
45
|
+
def __eq__(self, other):
|
|
46
|
+
return (self.service == other.service and
|
|
47
|
+
self.namespace == other.namespace and
|
|
48
|
+
self.event_type == other.event_type)
|
|
49
|
+
|
|
50
|
+
def __hash__(self):
|
|
51
|
+
return hash(self.namespace + self.service + str(self.event_type))
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Tencent is pleased to support the open source community by making polaris-python available.
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2021 Tencent. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the BSD 3-Clause License (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software distributed
|
|
12
|
+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
13
|
+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
14
|
+
# specific language governing permissions and limitations under the License.
|
|
15
|
+
|
|
16
|
+
from polaris.pkg.model.service import ServiceCallResult
|
|
17
|
+
from polaris.pkg.model.rule import GetServiceRuleRequest, ServiceRuleResponse
|
|
18
|
+
from polaris.pkg.model.server import (
|
|
19
|
+
InstanceDeRegisterRequest,
|
|
20
|
+
InstanceHeartbeatRequest,
|
|
21
|
+
InstanceRegisterRequest,
|
|
22
|
+
InstanceRegisterResponse,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseEngine(object):
|
|
27
|
+
def destroy(self):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def sync_get_one_instance(self, request):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def sync_get_instances(self, request):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def sync_register(self, request: InstanceRegisterRequest) -> InstanceRegisterResponse:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def sync_deregister(self, request: InstanceDeRegisterRequest) -> None:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
async def async_register(self, request: InstanceRegisterRequest) -> InstanceRegisterResponse:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
async def async_deregister(self, request: InstanceDeRegisterRequest) -> None:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
async def async_get_one_instance(self, request):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
async def async_get_instances(self):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def sync_get_services(self):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
async def async_get_services(self):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def sync_heartbeat(self, request: InstanceHeartbeatRequest) -> None:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
async def async_heartbeat(self, request: InstanceHeartbeatRequest) -> None:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
def sync_update_service_call_result(self, req: ServiceCallResult):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def sync_get_service_rule(self, event_type: int, req: GetServiceRuleRequest) -> ServiceRuleResponse:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def sync_lossless_register(self, request) -> None:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
async def async_lossless_register(self, request) -> None:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
def sync_lossless_deregister(self, request) -> None:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
async def async_lossless_deregister(self, request) -> None:
|
|
82
|
+
pass
|
|
83
|
+
|