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.
Files changed (99) hide show
  1. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/PKG-INFO +1 -1
  2. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/__init__.py +1 -1
  3. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/nacos.py +9 -1
  4. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/__init__.py +1 -1
  5. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/skill.py +0 -7
  6. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/__init__.py +4 -4
  7. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/skill.py +58 -42
  8. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/session.py +31 -3
  9. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/PKG-INFO +1 -1
  10. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/SOURCES.txt +1 -0
  11. dataops_sdk-0.5.4/tests/test_api_key_headers.py +74 -0
  12. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/README.md +0 -0
  13. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/pyproject.toml +0 -0
  14. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/setup.cfg +0 -0
  15. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/constants.py +0 -0
  16. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/exceptions.py +0 -0
  17. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/__init__.py +0 -0
  18. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/datasource_factory.py +0 -0
  19. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/__init__.py +0 -0
  20. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/config.py +0 -0
  21. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/constants.py +0 -0
  22. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/instance.py +0 -0
  23. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/nacos_exception.py +0 -0
  24. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_param.py +0 -0
  25. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_request.py +0 -0
  26. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/naming_response.py +0 -0
  27. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/preserved_metadata_key.py +0 -0
  28. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/schemas/nacos/service.py +0 -0
  29. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/__init__.py +0 -0
  30. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/common.py +0 -0
  31. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/dbConn_async.py +0 -0
  32. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/dbConn_base.py +0 -0
  33. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/dbConn_sync.py +0 -0
  34. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/duckdb_manager.py +0 -0
  35. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/file_transfer_base.py +0 -0
  36. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/ftp_file_transfer.py +0 -0
  37. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/neo4j_sync.py +0 -0
  38. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/queue_base.py +0 -0
  39. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/queue_kafka.py +0 -0
  40. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/sftp_file_transfer.py +0 -0
  41. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/runner/utils/websocket_client.py +0 -0
  42. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/api.py +0 -0
  43. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/common.py +0 -0
  44. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/data_table.py +0 -0
  45. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/datasource.py +0 -0
  46. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/dict_.py +0 -0
  47. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/enums.py +0 -0
  48. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/group.py +0 -0
  49. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/meta.py +0 -0
  50. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/ontology.py +0 -0
  51. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/query.py +0 -0
  52. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/report.py +0 -0
  53. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/table.py +0 -0
  54. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/tag.py +0 -0
  55. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/user.py +0 -0
  56. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/widget.py +0 -0
  57. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/wiki.py +0 -0
  58. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/schemas/workflow.py +0 -0
  59. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/api.py +0 -0
  60. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/auth.py +0 -0
  61. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/data_table.py +0 -0
  62. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/dataset.py +0 -0
  63. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/datasource.py +0 -0
  64. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/dict_.py +0 -0
  65. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/group.py +0 -0
  66. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/meta.py +0 -0
  67. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/metrics.py +0 -0
  68. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/ontology.py +0 -0
  69. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/ref.py +0 -0
  70. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/report.py +0 -0
  71. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/table.py +0 -0
  72. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/tag.py +0 -0
  73. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/widget.py +0 -0
  74. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/wiki.py +0 -0
  75. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/services/workflow.py +0 -0
  76. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/__init__.py +0 -0
  77. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/builder.py +0 -0
  78. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/constants.py +0 -0
  79. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/markdown_parser.py +0 -0
  80. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/models.py +0 -0
  81. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/parse_utils.py +0 -0
  82. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/render_utils.py +0 -0
  83. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/renderers.py +0 -0
  84. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/tiptap/tool.py +0 -0
  85. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/utils/__init__.py +0 -0
  86. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/utils/common.py +0 -0
  87. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/utils/log.py +0 -0
  88. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk/utils/utils.py +0 -0
  89. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/dependency_links.txt +0 -0
  90. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/requires.txt +0 -0
  91. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/src/dataops_sdk.egg-info/top_level.txt +0 -0
  92. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_constants.py +0 -0
  93. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_datasource_factory.py +0 -0
  94. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_db_conn_async.py +0 -0
  95. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_db_conn_sync.py +0 -0
  96. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_exceptions.py +0 -0
  97. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_session.py +0 -0
  98. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_utils.py +0 -0
  99. {dataops_sdk-0.5.3 → dataops_sdk-0.5.4}/tests/test_websocket_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataops-sdk
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Summary: Python SDK for the DataOps platform.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -93,4 +93,4 @@ from .services import ( # noqa: F401
93
93
  )
94
94
  from .session import dataops_base_url_context, request_context
95
95
 
96
- __version__ = "0.5.3"
96
+ __version__ = "0.5.4"
@@ -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, SkillOntologyVO
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
- "service_skill_list_ontologies_by_codes",
58
- "service_skill_list_ontology_options",
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) -> Meta[DataSkillSubFile]: ...
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/import",
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}/import",
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 = dict(path=path)
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) -> Meta[DataSkillSubFile]:
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
- f"/data/skill/{name}/subfile/getByName", content=to_json(encrypt_body(body))
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 Meta[DataSkillSubFile].model_validate(data)
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) -> Meta:
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
- f"/data/skill/{code}/subfile/getByCode", content=to_json(encrypt_body(body))
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 Meta.model_validate(data)
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
- 3. 模块级配置 / 本地缓存:``Fmind-Auth`` 取自 ``user_info``,``X-App-Code``
208
- 与 ``X-App-Release`` 取自 ``application`` 或 ``_config``。
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataops-sdk
3
- Version: 0.5.3
3
+ Version: 0.5.4
4
4
  Summary: Python SDK for the DataOps platform.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -86,6 +86,7 @@ src/dataops_sdk/utils/__init__.py
86
86
  src/dataops_sdk/utils/common.py
87
87
  src/dataops_sdk/utils/log.py
88
88
  src/dataops_sdk/utils/utils.py
89
+ tests/test_api_key_headers.py
89
90
  tests/test_constants.py
90
91
  tests/test_datasource_factory.py
91
92
  tests/test_db_conn_async.py
@@ -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