dataops-sdk 0.5.3__tar.gz → 0.5.4__tar.gz
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.
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/PKG-INFO +1 -1
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/__init__.py +1 -1
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/nacos.py +9 -1
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/__init__.py +1 -1
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/skill.py +0 -7
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/__init__.py +4 -4
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/skill.py +58 -42
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/session.py +31 -3
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/PKG-INFO +1 -1
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/SOURCES.txt +1 -0
- dataops_sdk-0.5.4/tests/test_api_key_headers.py +74 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/README.md +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/pyproject.toml +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/setup.cfg +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/constants.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/exceptions.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/__init__.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/datasource_factory.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/__init__.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/config.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/constants.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/instance.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/nacos_exception.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_param.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_request.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_response.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/preserved_metadata_key.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/service.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/__init__.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/common.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/dbConn_async.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/dbConn_base.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/dbConn_sync.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/duckdb_manager.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/file_transfer_base.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/ftp_file_transfer.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/neo4j_sync.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/queue_base.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/queue_kafka.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/sftp_file_transfer.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/websocket_client.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/api.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/common.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/data_table.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/datasource.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/dict_.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/enums.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/group.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/meta.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/ontology.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/query.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/report.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/table.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/tag.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/user.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/widget.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/wiki.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/workflow.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/api.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/auth.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/data_table.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/dataset.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/datasource.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/dict_.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/group.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/meta.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/metrics.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/ontology.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/ref.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/report.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/table.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/tag.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/widget.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/wiki.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/workflow.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/__init__.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/builder.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/constants.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/markdown_parser.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/models.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/parse_utils.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/render_utils.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/renderers.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/tool.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/utils/__init__.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/utils/common.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/utils/log.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/utils/utils.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/dependency_links.txt +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/requires.txt +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/top_level.txt +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_constants.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_datasource_factory.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_db_conn_async.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_db_conn_sync.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_exceptions.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_session.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_utils.py +0 -0
- {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_websocket_client.py +0 -0
|
@@ -22,6 +22,14 @@ from dataops_sdk.runner.schemas.nacos.constants import Constants
|
|
|
22
22
|
from dataops_sdk.session import get_headers
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _has_explicit_credential(headers: Mapping[str, str] | None) -> bool:
|
|
26
|
+
"""调用方已传入登录令牌或 API key 时,不再强制本地登录。"""
|
|
27
|
+
if not headers:
|
|
28
|
+
return False
|
|
29
|
+
names = {name.lower() for name in headers}
|
|
30
|
+
return "x-api-key" in names or "fmind-auth" in names
|
|
31
|
+
|
|
32
|
+
|
|
25
33
|
def get_service_instance(
|
|
26
34
|
service_name: str,
|
|
27
35
|
group_name: str = Constants.DEFAULT_GROUP,
|
|
@@ -89,7 +97,7 @@ def get_service_instance(
|
|
|
89
97
|
|
|
90
98
|
instance = random.choice(service.hosts)
|
|
91
99
|
|
|
92
|
-
auth_headers = get_headers()
|
|
100
|
+
auth_headers = dict(get_headers(strict=not _has_explicit_credential(headers)))
|
|
93
101
|
if headers:
|
|
94
102
|
auth_headers.update(headers)
|
|
95
103
|
|
|
@@ -47,7 +47,7 @@ from .ontology import (
|
|
|
47
47
|
)
|
|
48
48
|
from .query import PageParam, SqlCondition, SqlFilter, SqlQueryParam, SqlSort
|
|
49
49
|
from .report import ReportMeta, ReportMetaData
|
|
50
|
-
from .skill import DataSkill, DataSkillSubFile, MetaSkill, SkillCallResult
|
|
50
|
+
from .skill import DataSkill, DataSkillSubFile, MetaSkill, SkillCallResult
|
|
51
51
|
from .table import (
|
|
52
52
|
ColumnItem,
|
|
53
53
|
DataBaseColumnDictExternal,
|
|
@@ -62,13 +62,6 @@ class MetaSkill(Meta[DataSkill]):
|
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
class SkillOntologyVO(BaseModel):
|
|
66
|
-
"""技能归属本体(由 meta_release_ref 反查)."""
|
|
67
|
-
|
|
68
|
-
code: Annotated[str, Field(description="本体编码")]
|
|
69
|
-
title: Annotated[str, Field(description="本体标题", default="")]
|
|
70
|
-
|
|
71
|
-
|
|
72
65
|
class SkillCallResult(BaseModel):
|
|
73
66
|
"""调用 skill 后的返回结果,对应 Java ``SkillResult``.
|
|
74
67
|
|
|
@@ -101,10 +101,10 @@ from .skill import (
|
|
|
101
101
|
service_skill_update,
|
|
102
102
|
service_skill_get_subfile_by_code,
|
|
103
103
|
service_skill_get_subfile_by_name,
|
|
104
|
+
service_skill_files,
|
|
105
|
+
service_skill_file,
|
|
104
106
|
service_skill_upload,
|
|
105
107
|
service_skill_upload_sub_file,
|
|
106
|
-
service_skill_list_ontologies_by_codes,
|
|
107
|
-
service_skill_list_ontology_options,
|
|
108
108
|
service_skill_list_skill_codes_by_ontology,
|
|
109
109
|
)
|
|
110
110
|
from .table import (
|
|
@@ -249,10 +249,10 @@ __all__ = [
|
|
|
249
249
|
"service_skill_update",
|
|
250
250
|
"service_skill_get_subfile_by_code",
|
|
251
251
|
"service_skill_get_subfile_by_name",
|
|
252
|
+
"service_skill_files",
|
|
253
|
+
"service_skill_file",
|
|
252
254
|
"service_skill_upload",
|
|
253
255
|
"service_skill_upload_sub_file",
|
|
254
|
-
"service_skill_list_ontologies_by_codes",
|
|
255
|
-
"service_skill_list_ontology_options",
|
|
256
256
|
"service_skill_list_skill_codes_by_ontology",
|
|
257
257
|
"service_count_table_by_code",
|
|
258
258
|
"service_count_table_by_name",
|
|
@@ -21,13 +21,11 @@ from pydantic_core import to_json
|
|
|
21
21
|
from dataops_sdk.exceptions import AuthenticationError, DataOpsError
|
|
22
22
|
from dataops_sdk.schemas import (
|
|
23
23
|
DataSkill,
|
|
24
|
-
DataSkillSubFile,
|
|
25
24
|
Meta,
|
|
26
25
|
MetaBase,
|
|
27
26
|
MetaSkillSubType,
|
|
28
27
|
MetaType,
|
|
29
28
|
SkillCallResult,
|
|
30
|
-
SkillOntologyVO,
|
|
31
29
|
)
|
|
32
30
|
from dataops_sdk.services.meta import (
|
|
33
31
|
release_list_get_by_code,
|
|
@@ -54,8 +52,8 @@ __all__ = [
|
|
|
54
52
|
"service_skill_upload_sub_file",
|
|
55
53
|
"service_skill_get_subfile_by_name",
|
|
56
54
|
"service_skill_get_subfile_by_code",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
55
|
+
"service_skill_files",
|
|
56
|
+
"service_skill_file",
|
|
59
57
|
"service_skill_list_skill_codes_by_ontology",
|
|
60
58
|
]
|
|
61
59
|
|
|
@@ -227,26 +225,6 @@ def _post_skill_data_api(url: str, body: dict) -> object:
|
|
|
227
225
|
raise AuthenticationError("认证失败,请先登录") from e
|
|
228
226
|
|
|
229
227
|
|
|
230
|
-
def service_skill_list_ontology_options() -> list[SkillOntologyVO]:
|
|
231
|
-
"""列出已绑定技能的本体选项(``POST /data/skill/ontology/options``)."""
|
|
232
|
-
data = _post_skill_data_api("/data/skill/ontology/options", {})
|
|
233
|
-
if not isinstance(data, list):
|
|
234
|
-
return []
|
|
235
|
-
return [SkillOntologyVO.model_validate(item) for item in data]
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def service_skill_list_ontologies_by_codes(codes: list[str]) -> dict[str, list[SkillOntologyVO]]:
|
|
239
|
-
"""批量查询技能归属本体(``POST /data/skill/ontology/batch``)."""
|
|
240
|
-
data = _post_skill_data_api("/data/skill/ontology/batch", {"codes": codes})
|
|
241
|
-
if not isinstance(data, dict):
|
|
242
|
-
return {}
|
|
243
|
-
result: dict[str, list[SkillOntologyVO]] = {}
|
|
244
|
-
for key, value in data.items():
|
|
245
|
-
if isinstance(value, list):
|
|
246
|
-
result[str(key)] = [SkillOntologyVO.model_validate(item) for item in value]
|
|
247
|
-
return result
|
|
248
|
-
|
|
249
|
-
|
|
250
228
|
def service_skill_list_skill_codes_by_ontology(ontology_code: str) -> list[str]:
|
|
251
229
|
"""按本体查询绑定的技能 code 列表(``POST /data/skill/ontology/skillCodes``)."""
|
|
252
230
|
data = _post_skill_data_api(
|
|
@@ -280,12 +258,10 @@ def service_get_skill_by_name(name: str, path: None = None) -> Meta[DataSkill]:
|
|
|
280
258
|
|
|
281
259
|
|
|
282
260
|
@overload
|
|
283
|
-
def service_get_skill_by_name(name: str, path: str) ->
|
|
261
|
+
def service_get_skill_by_name(name: str, path: str) -> dict: ...
|
|
284
262
|
|
|
285
263
|
|
|
286
|
-
def service_get_skill_by_name(
|
|
287
|
-
name: str, path: str | None = None
|
|
288
|
-
) -> Meta[DataSkill | DataSkillSubFile]:
|
|
264
|
+
def service_get_skill_by_name(name: str, path: str | None = None) -> Meta[DataSkill] | dict:
|
|
289
265
|
"""按业务名获取技能详情.
|
|
290
266
|
|
|
291
267
|
Args:
|
|
@@ -360,7 +336,7 @@ def service_skill_upload(
|
|
|
360
336
|
with (
|
|
361
337
|
open(path, "rb") as f,
|
|
362
338
|
post_multipart(
|
|
363
|
-
"/data/skill/
|
|
339
|
+
"/data/skill/create",
|
|
364
340
|
files={"file": (path.name, f)},
|
|
365
341
|
data=form_data,
|
|
366
342
|
) as resp,
|
|
@@ -410,7 +386,7 @@ def service_skill_update(
|
|
|
410
386
|
with (
|
|
411
387
|
open(path, "rb") as f,
|
|
412
388
|
post_multipart(
|
|
413
|
-
f"/data/skill/{meta.code}/
|
|
389
|
+
f"/data/skill/{meta.code}/update",
|
|
414
390
|
files={"file": (path.name, f)},
|
|
415
391
|
data=form_data or None,
|
|
416
392
|
) as resp,
|
|
@@ -549,12 +525,10 @@ def service_skill_delete_sub_file(name: str, path: str) -> None:
|
|
|
549
525
|
DataOpsError: API 返回错误时
|
|
550
526
|
AuthenticationError: 未认证或认证失败时
|
|
551
527
|
"""
|
|
552
|
-
body =
|
|
528
|
+
body = {"path": path}
|
|
553
529
|
try:
|
|
554
530
|
with get_session() as session:
|
|
555
|
-
resp = session.post(
|
|
556
|
-
f"/data/skill/{name}/subfile/delete", content=to_json(encrypt_body(body))
|
|
557
|
-
)
|
|
531
|
+
resp = session.post(f"/data/skill/{name}/subfile/delete", params=body)
|
|
558
532
|
resp_body = resp.json()
|
|
559
533
|
if resp.status_code == 401:
|
|
560
534
|
raise AuthenticationError("认证失败,请先登录")
|
|
@@ -639,7 +613,7 @@ def service_skill_call_by_name(name: str, inputs: Mapping) -> SkillCallResult:
|
|
|
639
613
|
raise AuthenticationError("认证失败,请先登录") from e
|
|
640
614
|
|
|
641
615
|
|
|
642
|
-
def service_skill_get_subfile_by_name(name: str, path: str) ->
|
|
616
|
+
def service_skill_get_subfile_by_name(name: str, path: str) -> dict:
|
|
643
617
|
"""获取技能下的指定子文件.
|
|
644
618
|
|
|
645
619
|
Args:
|
|
@@ -653,11 +627,10 @@ def service_skill_get_subfile_by_name(name: str, path: str) -> Meta[DataSkillSub
|
|
|
653
627
|
DataOpsError: API 返回错误时
|
|
654
628
|
AuthenticationError: 未认证或认证失败时
|
|
655
629
|
"""
|
|
656
|
-
body = dict(path=path)
|
|
657
630
|
try:
|
|
658
631
|
with get_session() as session:
|
|
659
632
|
resp = session.post(
|
|
660
|
-
|
|
633
|
+
"/data/skill/file", content=to_json(encrypt_body({"name": name, "path": path}))
|
|
661
634
|
)
|
|
662
635
|
resp_body = resp.json()
|
|
663
636
|
if resp.status_code == 401:
|
|
@@ -669,12 +642,12 @@ def service_skill_get_subfile_by_name(name: str, path: str) -> Meta[DataSkillSub
|
|
|
669
642
|
data = resp_body.get("data")
|
|
670
643
|
if data is None:
|
|
671
644
|
raise DataOpsError(f"文件 {path} 不存在")
|
|
672
|
-
return
|
|
645
|
+
return data
|
|
673
646
|
except SystemExit as e:
|
|
674
647
|
raise AuthenticationError("认证失败,请先登录") from e
|
|
675
648
|
|
|
676
649
|
|
|
677
|
-
def service_skill_get_subfile_by_code(code: str, path: str) ->
|
|
650
|
+
def service_skill_get_subfile_by_code(code: str, path: str) -> dict:
|
|
678
651
|
"""获取技能下的指定子文件.
|
|
679
652
|
|
|
680
653
|
Args:
|
|
@@ -688,11 +661,10 @@ def service_skill_get_subfile_by_code(code: str, path: str) -> Meta:
|
|
|
688
661
|
DataOpsError: API 返回错误时
|
|
689
662
|
AuthenticationError: 未认证或认证失败时
|
|
690
663
|
"""
|
|
691
|
-
body = dict(path=path)
|
|
692
664
|
try:
|
|
693
665
|
with get_session() as session:
|
|
694
666
|
resp = session.post(
|
|
695
|
-
|
|
667
|
+
"/data/skill/file", content=to_json(encrypt_body({"code": code, "path": path}))
|
|
696
668
|
)
|
|
697
669
|
resp_body = resp.json()
|
|
698
670
|
if resp.status_code == 401:
|
|
@@ -704,6 +676,50 @@ def service_skill_get_subfile_by_code(code: str, path: str) -> Meta:
|
|
|
704
676
|
data = resp_body.get("data")
|
|
705
677
|
if data is None:
|
|
706
678
|
raise DataOpsError(f"文件 {path} 不存在")
|
|
707
|
-
return
|
|
679
|
+
return data
|
|
680
|
+
except SystemExit as e:
|
|
681
|
+
raise AuthenticationError("认证失败,请先登录") from e
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def service_skill_files(*, code: str | None = None, name: str | None = None) -> list[dict]:
|
|
685
|
+
"""获取技能文件树,技能由 ``code`` 或 ``name`` 定位。"""
|
|
686
|
+
if not code and not name:
|
|
687
|
+
raise ValueError("code 或 name 至少提供一个")
|
|
688
|
+
try:
|
|
689
|
+
with get_session() as session:
|
|
690
|
+
resp = session.post(
|
|
691
|
+
"/data/skill/files",
|
|
692
|
+
content=to_json(encrypt_body({"code": code, "name": name})),
|
|
693
|
+
)
|
|
694
|
+
body = resp.json()
|
|
695
|
+
if resp.status_code == 401:
|
|
696
|
+
raise AuthenticationError("认证失败,请先登录")
|
|
697
|
+
if resp.status_code != 200:
|
|
698
|
+
raise DataOpsError(body.get("msg", f"HTTP {resp.status_code}"), resp.status_code)
|
|
699
|
+
data = body.get("data")
|
|
700
|
+
return data if isinstance(data, list) else []
|
|
701
|
+
except SystemExit as e:
|
|
702
|
+
raise AuthenticationError("认证失败,请先登录") from e
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def service_skill_file(path: str, *, code: str | None = None, name: str | None = None) -> dict:
|
|
706
|
+
"""读取技能指定路径的可读内容;``SKILL.md`` 对应技能主文件。"""
|
|
707
|
+
if not code and not name:
|
|
708
|
+
raise ValueError("code 或 name 至少提供一个")
|
|
709
|
+
try:
|
|
710
|
+
with get_session() as session:
|
|
711
|
+
resp = session.post(
|
|
712
|
+
"/data/skill/file",
|
|
713
|
+
content=to_json(encrypt_body({"code": code, "name": name, "path": path})),
|
|
714
|
+
)
|
|
715
|
+
body = resp.json()
|
|
716
|
+
if resp.status_code == 401:
|
|
717
|
+
raise AuthenticationError("认证失败,请先登录")
|
|
718
|
+
if resp.status_code != 200:
|
|
719
|
+
raise DataOpsError(body.get("msg", f"HTTP {resp.status_code}"), resp.status_code)
|
|
720
|
+
data = body.get("data")
|
|
721
|
+
if not isinstance(data, dict):
|
|
722
|
+
raise DataOpsError(f"文件 {path} 不存在")
|
|
723
|
+
return data
|
|
708
724
|
except SystemExit as e:
|
|
709
725
|
raise AuthenticationError("认证失败,请先登录") from e
|
|
@@ -198,18 +198,38 @@ def get_user_info(strict: bool = True) -> UserInfo | None:
|
|
|
198
198
|
return user_info
|
|
199
199
|
|
|
200
200
|
|
|
201
|
+
def _api_key_headers_from_env() -> dict[str, str] | None:
|
|
202
|
+
"""从环境变量组装 API key 请求头。
|
|
203
|
+
|
|
204
|
+
``DATAOPS_API_KEY`` 与 ``DATAOPS_CLIENT_ID`` 必须同时存在才视为 API key 认证。
|
|
205
|
+
``DATAOPS_TENANT_ID`` 非空时写入 ``x-tenant-id``。
|
|
206
|
+
"""
|
|
207
|
+
api_key = (os.getenv("DATAOPS_API_KEY") or "").strip()
|
|
208
|
+
client_id = (os.getenv("DATAOPS_CLIENT_ID") or "").strip()
|
|
209
|
+
tenant_id = (os.getenv("DATAOPS_TENANT_ID") or "").strip()
|
|
210
|
+
if not api_key or not client_id or not tenant_id:
|
|
211
|
+
return None
|
|
212
|
+
headers = {"x-api-key": api_key, "x-client-id": client_id, "x-tenant-id": tenant_id}
|
|
213
|
+
return headers
|
|
214
|
+
|
|
215
|
+
|
|
201
216
|
def get_headers(strict: bool = True) -> dict[str, str]:
|
|
202
217
|
"""构造调用 DataOps API 所需的请求头。
|
|
203
218
|
|
|
204
219
|
按以下优先级合并来源:
|
|
205
220
|
1. 请求上下文中的认证头(web app 场景,由 ``set_request_auth`` 注入)。
|
|
206
|
-
2. 环境变量 ``FMIND_AUTH_HEADERS`` 中的 JSON
|
|
207
|
-
|
|
208
|
-
|
|
221
|
+
2. 环境变量 ``FMIND_AUTH_HEADERS`` 中的 JSON 字符串。可为登录令牌,也可仅为
|
|
222
|
+
``x-api-key`` / ``x-client-id`` / ``x-tenant-id``。
|
|
223
|
+
3. 环境变量 ``DATAOPS_API_KEY`` 与 ``DATAOPS_CLIENT_ID``(可选 ``DATAOPS_TENANT_ID``)。
|
|
224
|
+
存在时不再要求 ``user_info.json``,也不写入 ``Fmind-Auth``。若本地有
|
|
225
|
+
``application.json``,仍附加 ``X-App-Code`` / ``X-App-Release``。
|
|
226
|
+
4. 模块级配置 / 本地缓存:``Fmind-Auth`` 取自 ``user_info``,``X-App-Code``
|
|
227
|
+
与 ``X-App-Release`` 取自 ``application``。
|
|
209
228
|
|
|
210
229
|
Args:
|
|
211
230
|
strict: 是否要求凭据必须可用。为 ``True`` 时若缺失则会触发 ``SystemExit``;
|
|
212
231
|
为 ``False`` 时允许仅返回 ``X-App-Code`` 等应用头而不包含 ``Fmind-Auth``。
|
|
232
|
+
API key 环境变量视为完整凭证,满足 ``strict=True``。
|
|
213
233
|
|
|
214
234
|
Returns:
|
|
215
235
|
Mapping[str, str]: 最终的请求头映射。
|
|
@@ -226,6 +246,14 @@ def get_headers(strict: bool = True) -> dict[str, str]:
|
|
|
226
246
|
headers = from_json(fmind_auth_headers)
|
|
227
247
|
assert isinstance(headers, dict), "FMIND_AUTH_HEADERS must be a JSON object string"
|
|
228
248
|
return headers
|
|
249
|
+
api_key_headers = _api_key_headers_from_env()
|
|
250
|
+
if api_key_headers is not None:
|
|
251
|
+
headers = dict(api_key_headers)
|
|
252
|
+
application = get_application(strict=False)
|
|
253
|
+
if application is not None:
|
|
254
|
+
headers["X-App-Code"] = application.CODE
|
|
255
|
+
headers["X-App-Release"] = "1.0.0"
|
|
256
|
+
return headers
|
|
229
257
|
# 其次从配置获取
|
|
230
258
|
user_info = get_user_info(strict=strict)
|
|
231
259
|
application = get_application(strict=strict)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""API key 作为 dataops_sdk 完整凭证的请求头测试."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from dataops_sdk.session import get_headers
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_fmind_auth_headers_api_key_is_returned_as_is(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
12
|
+
"""FMIND_AUTH_HEADERS 仅含 API key 三头时原样返回,不补 Fmind-Auth."""
|
|
13
|
+
payload = {
|
|
14
|
+
"x-api-key": "secret",
|
|
15
|
+
"x-client-id": "caller-client",
|
|
16
|
+
"x-tenant-id": "tenant-1",
|
|
17
|
+
}
|
|
18
|
+
monkeypatch.setenv("FMIND_AUTH_HEADERS", json.dumps(payload))
|
|
19
|
+
monkeypatch.setenv("DATAOPS_API_KEY", "other-key")
|
|
20
|
+
monkeypatch.setenv("DATAOPS_CLIENT_ID", "other-client")
|
|
21
|
+
|
|
22
|
+
headers = get_headers(strict=True)
|
|
23
|
+
|
|
24
|
+
assert headers == payload
|
|
25
|
+
assert "Fmind-Auth" not in headers
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_api_key_env_satisfies_strict_without_user_info(
|
|
29
|
+
monkeypatch: pytest.MonkeyPatch, tmp_user_dir
|
|
30
|
+
) -> None:
|
|
31
|
+
"""仅 DATAOPS_API_KEY 与 DATAOPS_CLIENT_ID 时 strict=True 成功,且不含 Fmind-Auth."""
|
|
32
|
+
monkeypatch.delenv("FMIND_AUTH_HEADERS", raising=False)
|
|
33
|
+
monkeypatch.setenv("DATAOPS_API_KEY", "secret")
|
|
34
|
+
monkeypatch.setenv("DATAOPS_CLIENT_ID", "caller-client")
|
|
35
|
+
monkeypatch.setenv("DATAOPS_TENANT_ID", "tenant-1")
|
|
36
|
+
|
|
37
|
+
headers = get_headers(strict=True)
|
|
38
|
+
|
|
39
|
+
assert headers["x-api-key"] == "secret"
|
|
40
|
+
assert headers["x-client-id"] == "caller-client"
|
|
41
|
+
assert headers["x-tenant-id"] == "tenant-1"
|
|
42
|
+
assert "Fmind-Auth" not in headers
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_api_key_env_appends_application_headers(
|
|
46
|
+
monkeypatch: pytest.MonkeyPatch, write_application
|
|
47
|
+
) -> None:
|
|
48
|
+
"""存在 application.json 时,API key 头仍附加应用编码与版本."""
|
|
49
|
+
monkeypatch.delenv("FMIND_AUTH_HEADERS", raising=False)
|
|
50
|
+
monkeypatch.setenv("DATAOPS_API_KEY", "secret")
|
|
51
|
+
monkeypatch.setenv("DATAOPS_CLIENT_ID", "caller-client")
|
|
52
|
+
monkeypatch.delenv("DATAOPS_TENANT_ID", raising=False)
|
|
53
|
+
write_application({"CODE": "file-app"})
|
|
54
|
+
|
|
55
|
+
headers = get_headers(strict=True)
|
|
56
|
+
|
|
57
|
+
assert headers["x-api-key"] == "secret"
|
|
58
|
+
assert headers["X-App-Code"] == "file-app"
|
|
59
|
+
assert headers["X-App-Release"] == "1.0.0"
|
|
60
|
+
assert "Fmind-Auth" not in headers
|
|
61
|
+
assert "x-tenant-id" not in headers
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_api_key_without_client_id_still_requires_login(
|
|
65
|
+
monkeypatch: pytest.MonkeyPatch, tmp_user_dir
|
|
66
|
+
) -> None:
|
|
67
|
+
"""只设置 API key、没有 client id 时,strict=True 仍要求登录."""
|
|
68
|
+
monkeypatch.delenv("FMIND_AUTH_HEADERS", raising=False)
|
|
69
|
+
monkeypatch.setenv("DATAOPS_API_KEY", "secret")
|
|
70
|
+
monkeypatch.delenv("DATAOPS_CLIENT_ID", raising=False)
|
|
71
|
+
|
|
72
|
+
with pytest.raises(SystemExit) as exc:
|
|
73
|
+
get_headers(strict=True)
|
|
74
|
+
assert exc.value.code == 401
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/nacos_exception.py
RENAMED
|
File without changes
|
{dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_param.py
RENAMED
|
File without changes
|
{dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_request.py
RENAMED
|
File without changes
|
{dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|