contractforge-fabric 0.1.0__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.
- contractforge_fabric-0.1.0/.gitignore +19 -0
- contractforge_fabric-0.1.0/CHANGELOG.md +9 -0
- contractforge_fabric-0.1.0/PKG-INFO +81 -0
- contractforge_fabric-0.1.0/README.md +52 -0
- contractforge_fabric-0.1.0/pyproject.toml +46 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/__init__.py +28 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/adapter.py +63 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/api.py +36 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/capabilities/__init__.py +5 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/capabilities/lakehouse.py +51 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/cli.py +80 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/diagnostics.py +38 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/environment.py +48 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/rendering/__init__.py +5 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/rendering/review.py +222 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/sources/__init__.py +16 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/sources/classification.py +182 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/sources/support.py +33 -0
- contractforge_fabric-0.1.0/src/contractforge_fabric/subtargets.py +27 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `contractforge-fabric` are documented in this file.
|
|
4
|
+
|
|
5
|
+
## 0.1.0
|
|
6
|
+
|
|
7
|
+
- Added the initial render-only Fabric Lakehouse adapter package.
|
|
8
|
+
- Added conservative capabilities, source-support classification, review
|
|
9
|
+
artifacts and CLI commands.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: contractforge-fabric
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Microsoft Fabric adapter for ContractForge Core.
|
|
5
|
+
Project-URL: Homepage, https://github.com/marquesantero/contractforge-core/tree/main/adapters/fabric
|
|
6
|
+
Project-URL: Documentation, https://marquesantero.github.io/contractforge-core/docs/adapters/fabric
|
|
7
|
+
Project-URL: Repository, https://github.com/marquesantero/contractforge-core
|
|
8
|
+
Project-URL: Issues, https://github.com/marquesantero/contractforge-core/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/marquesantero/contractforge-core/blob/main/adapters/fabric/CHANGELOG.md
|
|
10
|
+
Author: ContractForge contributors
|
|
11
|
+
License: MIT
|
|
12
|
+
Keywords: contractforge,data-contracts,ingestion,lakehouse,microsoft-fabric,onelake
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: contractforge-core<0.2,>=0.1
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
27
|
+
Provides-Extra: runtime
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# contractforge-fabric
|
|
31
|
+
|
|
32
|
+
`contractforge-fabric` is the Microsoft Fabric adapter package for
|
|
33
|
+
ContractForge.
|
|
34
|
+
|
|
35
|
+
The first adapter slice is intentionally conservative. It plans contracts
|
|
36
|
+
against a Fabric Lakehouse target, renders review artifacts, publishes a
|
|
37
|
+
machine-readable source-support catalog and documents the runtime boundaries for
|
|
38
|
+
future deployment work.
|
|
39
|
+
|
|
40
|
+
It does not yet call Fabric REST APIs, submit notebooks, create Data Factory
|
|
41
|
+
pipelines or mutate Fabric workspaces.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install contractforge-core contractforge-fabric
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Use
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from contractforge_fabric import plan_fabric_contract, render_fabric_contract
|
|
53
|
+
|
|
54
|
+
contract = {
|
|
55
|
+
"source": {"type": "parquet", "path": "Files/orders"},
|
|
56
|
+
"target": {"catalog": "workspace", "schema": "bronze", "table": "orders"},
|
|
57
|
+
"mode": "overwrite",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
planning = plan_fabric_contract(contract)
|
|
61
|
+
artifacts = render_fabric_contract(contract)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
CLI:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
contractforge-fabric plan contracts/orders.ingestion.yaml
|
|
68
|
+
contractforge-fabric render contracts/orders.ingestion.yaml
|
|
69
|
+
contractforge-fabric sources
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Current scope
|
|
73
|
+
|
|
74
|
+
- Subtarget: `fabric_lakehouse`.
|
|
75
|
+
- Runtime status: render-only.
|
|
76
|
+
- Native concepts: Fabric Workspace, Lakehouse, Warehouse, OneLake, shortcuts,
|
|
77
|
+
notebooks and Data Factory pipelines.
|
|
78
|
+
- Evidence store target: Fabric Lakehouse Delta tables, review-only for now.
|
|
79
|
+
|
|
80
|
+
The adapter returns `REVIEW_REQUIRED` for semantics that need a concrete Fabric
|
|
81
|
+
runtime design before execution can be claimed.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# contractforge-fabric
|
|
2
|
+
|
|
3
|
+
`contractforge-fabric` is the Microsoft Fabric adapter package for
|
|
4
|
+
ContractForge.
|
|
5
|
+
|
|
6
|
+
The first adapter slice is intentionally conservative. It plans contracts
|
|
7
|
+
against a Fabric Lakehouse target, renders review artifacts, publishes a
|
|
8
|
+
machine-readable source-support catalog and documents the runtime boundaries for
|
|
9
|
+
future deployment work.
|
|
10
|
+
|
|
11
|
+
It does not yet call Fabric REST APIs, submit notebooks, create Data Factory
|
|
12
|
+
pipelines or mutate Fabric workspaces.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install contractforge-core contractforge-fabric
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Use
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from contractforge_fabric import plan_fabric_contract, render_fabric_contract
|
|
24
|
+
|
|
25
|
+
contract = {
|
|
26
|
+
"source": {"type": "parquet", "path": "Files/orders"},
|
|
27
|
+
"target": {"catalog": "workspace", "schema": "bronze", "table": "orders"},
|
|
28
|
+
"mode": "overwrite",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
planning = plan_fabric_contract(contract)
|
|
32
|
+
artifacts = render_fabric_contract(contract)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
CLI:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
contractforge-fabric plan contracts/orders.ingestion.yaml
|
|
39
|
+
contractforge-fabric render contracts/orders.ingestion.yaml
|
|
40
|
+
contractforge-fabric sources
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Current scope
|
|
44
|
+
|
|
45
|
+
- Subtarget: `fabric_lakehouse`.
|
|
46
|
+
- Runtime status: render-only.
|
|
47
|
+
- Native concepts: Fabric Workspace, Lakehouse, Warehouse, OneLake, shortcuts,
|
|
48
|
+
notebooks and Data Factory pipelines.
|
|
49
|
+
- Evidence store target: Fabric Lakehouse Delta tables, review-only for now.
|
|
50
|
+
|
|
51
|
+
The adapter returns `REVIEW_REQUIRED` for semantics that need a concrete Fabric
|
|
52
|
+
runtime design before execution can be claimed.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "contractforge-fabric"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Microsoft Fabric adapter for ContractForge Core."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "ContractForge contributors" }]
|
|
13
|
+
keywords = ["contractforge", "microsoft-fabric", "onelake", "lakehouse", "data-contracts", "ingestion"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Database",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
]
|
|
26
|
+
dependencies = ["contractforge-core>=0.1,<0.2"]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/marquesantero/contractforge-core/tree/main/adapters/fabric"
|
|
30
|
+
Documentation = "https://marquesantero.github.io/contractforge-core/docs/adapters/fabric"
|
|
31
|
+
Repository = "https://github.com/marquesantero/contractforge-core"
|
|
32
|
+
Issues = "https://github.com/marquesantero/contractforge-core/issues"
|
|
33
|
+
Changelog = "https://github.com/marquesantero/contractforge-core/blob/main/adapters/fabric/CHANGELOG.md"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = ["pytest>=8"]
|
|
37
|
+
runtime = []
|
|
38
|
+
|
|
39
|
+
[project.scripts]
|
|
40
|
+
contractforge-fabric = "contractforge_fabric.cli:main"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/contractforge_fabric"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.sdist]
|
|
46
|
+
include = ["/src/contractforge_fabric", "/README.md", "/CHANGELOG.md", "/pyproject.toml"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Microsoft Fabric adapter for ContractForge."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
from contractforge_fabric.adapter import FabricAdapter
|
|
8
|
+
from contractforge_fabric.api import plan_fabric_contract, render_fabric_contract
|
|
9
|
+
from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE, fabric_lakehouse_capabilities
|
|
10
|
+
from contractforge_fabric.sources import fabric_source_support, list_fabric_source_support
|
|
11
|
+
from contractforge_fabric.subtargets import adapter_for_subtarget, list_fabric_subtargets
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
__version__ = version("contractforge-fabric")
|
|
15
|
+
except PackageNotFoundError: # pragma: no cover
|
|
16
|
+
__version__ = "0.0.0"
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"FABRIC_SUBTARGET_LAKEHOUSE",
|
|
20
|
+
"FabricAdapter",
|
|
21
|
+
"adapter_for_subtarget",
|
|
22
|
+
"fabric_lakehouse_capabilities",
|
|
23
|
+
"fabric_source_support",
|
|
24
|
+
"list_fabric_source_support",
|
|
25
|
+
"list_fabric_subtargets",
|
|
26
|
+
"plan_fabric_contract",
|
|
27
|
+
"render_fabric_contract",
|
|
28
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""ContractForge adapter implementation for Microsoft Fabric targets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from contractforge_core.adapters import RenderedArtifacts
|
|
9
|
+
from contractforge_core.capabilities import PlatformCapabilities
|
|
10
|
+
from contractforge_core.planner import ExecutionPlan, PlanningResult, plan_contract
|
|
11
|
+
from contractforge_core.semantic import SemanticContract
|
|
12
|
+
from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE, fabric_lakehouse_capabilities
|
|
13
|
+
from contractforge_fabric.diagnostics import fabric_planning_warnings, unsupported_source_blockers
|
|
14
|
+
from contractforge_fabric.environment import FabricEnvironment
|
|
15
|
+
from contractforge_fabric.rendering import render_fabric_review_artifacts
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class FabricAdapter:
|
|
20
|
+
"""Fabric Lakehouse adapter for planning and review artifact rendering."""
|
|
21
|
+
|
|
22
|
+
declared_capabilities: PlatformCapabilities
|
|
23
|
+
environment: FabricEnvironment = FabricEnvironment()
|
|
24
|
+
name: str = FABRIC_SUBTARGET_LAKEHOUSE
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def lakehouse(cls, environment: dict[str, Any] | None = None) -> "FabricAdapter":
|
|
28
|
+
return cls(
|
|
29
|
+
fabric_lakehouse_capabilities(),
|
|
30
|
+
environment=FabricEnvironment.from_contract(environment),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def capabilities(self) -> PlatformCapabilities:
|
|
34
|
+
return self.declared_capabilities
|
|
35
|
+
|
|
36
|
+
def plan(self, contract: SemanticContract) -> PlanningResult:
|
|
37
|
+
source_blockers = unsupported_source_blockers(contract)
|
|
38
|
+
if source_blockers:
|
|
39
|
+
return PlanningResult(status="UNSUPPORTED", plan=None, blockers=source_blockers)
|
|
40
|
+
|
|
41
|
+
result = plan_contract(contract, self.capabilities())
|
|
42
|
+
warnings = result.warnings + fabric_planning_warnings(contract)
|
|
43
|
+
if result.status == "SUPPORTED":
|
|
44
|
+
return PlanningResult(status="SUPPORTED_WITH_WARNINGS", plan=result.plan, warnings=warnings)
|
|
45
|
+
return PlanningResult(
|
|
46
|
+
status=result.status,
|
|
47
|
+
plan=result.plan,
|
|
48
|
+
blockers=result.blockers,
|
|
49
|
+
warnings=warnings,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def render(self, plan: ExecutionPlan) -> RenderedArtifacts:
|
|
53
|
+
return render_fabric_review_artifacts(plan=plan, planning=None, environment=self.environment)
|
|
54
|
+
|
|
55
|
+
def render_contract(self, contract: SemanticContract, *, raw_contract: dict[str, Any] | None = None) -> RenderedArtifacts:
|
|
56
|
+
planning = self.plan(contract)
|
|
57
|
+
return render_fabric_review_artifacts(
|
|
58
|
+
plan=planning.plan,
|
|
59
|
+
planning=planning,
|
|
60
|
+
contract=contract,
|
|
61
|
+
raw_contract=raw_contract,
|
|
62
|
+
environment=self.environment,
|
|
63
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""High-level Fabric adapter API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from contractforge_core.adapters import RenderedArtifacts
|
|
8
|
+
from contractforge_core.contracts import semantic_contract_from_mapping
|
|
9
|
+
from contractforge_core.planner import PlanningResult
|
|
10
|
+
from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE
|
|
11
|
+
from contractforge_fabric.subtargets import adapter_for_subtarget
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def plan_fabric_contract(
|
|
15
|
+
contract: dict[str, Any],
|
|
16
|
+
*,
|
|
17
|
+
subtarget: str = FABRIC_SUBTARGET_LAKEHOUSE,
|
|
18
|
+
environment: dict[str, Any] | None = None,
|
|
19
|
+
) -> PlanningResult:
|
|
20
|
+
semantic = semantic_contract_from_mapping(contract)
|
|
21
|
+
adapter = adapter_for_subtarget(subtarget, environment=environment)
|
|
22
|
+
return adapter.plan(semantic)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def render_fabric_contract(
|
|
26
|
+
contract: dict[str, Any],
|
|
27
|
+
*,
|
|
28
|
+
subtarget: str = FABRIC_SUBTARGET_LAKEHOUSE,
|
|
29
|
+
environment: dict[str, Any] | None = None,
|
|
30
|
+
) -> RenderedArtifacts:
|
|
31
|
+
semantic = semantic_contract_from_mapping(contract)
|
|
32
|
+
adapter = adapter_for_subtarget(subtarget, environment=environment)
|
|
33
|
+
return adapter.render_contract(semantic, raw_contract=contract)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ["plan_fabric_contract", "render_fabric_contract"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Capability declaration for the Microsoft Fabric Lakehouse adapter target."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contractforge_core.capabilities import PlatformCapabilities
|
|
6
|
+
|
|
7
|
+
FABRIC_SUBTARGET_LAKEHOUSE = "fabric_lakehouse"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def fabric_lakehouse_capabilities() -> PlatformCapabilities:
|
|
11
|
+
"""Return conservative capabilities for the initial Fabric target.
|
|
12
|
+
|
|
13
|
+
Fabric has native Lakehouse, Warehouse, OneLake, notebook and Data Factory
|
|
14
|
+
surfaces. This first adapter slice is render-only, so execution-sensitive
|
|
15
|
+
semantics remain review-required until runtime evidence exists.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
return PlatformCapabilities(
|
|
19
|
+
platform=FABRIC_SUBTARGET_LAKEHOUSE,
|
|
20
|
+
supports_append=True,
|
|
21
|
+
supports_overwrite=True,
|
|
22
|
+
supports_merge=True,
|
|
23
|
+
supports_hash_diff=False,
|
|
24
|
+
supports_scd2=False,
|
|
25
|
+
supports_snapshot_soft_delete=False,
|
|
26
|
+
supports_schema_evolution=True,
|
|
27
|
+
supports_row_filters=False,
|
|
28
|
+
supports_column_masks=False,
|
|
29
|
+
supports_available_now_streaming=False,
|
|
30
|
+
supports_required_columns_quality=True,
|
|
31
|
+
supports_unique_key_quality=True,
|
|
32
|
+
supports_max_null_ratio_quality=True,
|
|
33
|
+
supports_expression_quality=True,
|
|
34
|
+
supports_shape=True,
|
|
35
|
+
supports_transform=True,
|
|
36
|
+
evidence_stores=("fabric_lakehouse_delta_tables",),
|
|
37
|
+
review_required_semantics=(
|
|
38
|
+
"hash_diff_upsert",
|
|
39
|
+
"scd2_historical",
|
|
40
|
+
"snapshot_soft_delete",
|
|
41
|
+
"available_now_streaming",
|
|
42
|
+
"row_filters",
|
|
43
|
+
"column_masks",
|
|
44
|
+
"source.jdbc",
|
|
45
|
+
"source.rest_api",
|
|
46
|
+
"source.http_file",
|
|
47
|
+
"source.kafka_bounded",
|
|
48
|
+
"source.eventhubs_bounded",
|
|
49
|
+
"source.native_passthrough",
|
|
50
|
+
),
|
|
51
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Minimal CLI entry point for the Fabric adapter package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
from contractforge_fabric.api import plan_fabric_contract, render_fabric_contract
|
|
13
|
+
from contractforge_fabric.sources import list_fabric_source_support
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(argv: list[str] | None = None) -> int:
|
|
17
|
+
parser = argparse.ArgumentParser(prog="contractforge-fabric")
|
|
18
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
19
|
+
|
|
20
|
+
plan = subparsers.add_parser("plan", help="Plan a contract against the Fabric Lakehouse target.")
|
|
21
|
+
plan.add_argument("contract", type=Path)
|
|
22
|
+
plan.add_argument("--environment", type=Path)
|
|
23
|
+
|
|
24
|
+
render = subparsers.add_parser("render", help="Render Fabric planning review artifacts.")
|
|
25
|
+
render.add_argument("contract", type=Path)
|
|
26
|
+
render.add_argument("--environment", type=Path)
|
|
27
|
+
|
|
28
|
+
subparsers.add_parser("sources", help="Print Fabric source support metadata.")
|
|
29
|
+
|
|
30
|
+
args = parser.parse_args(argv)
|
|
31
|
+
if args.command == "plan":
|
|
32
|
+
return _handle_plan(args.contract, args.environment)
|
|
33
|
+
if args.command == "render":
|
|
34
|
+
return _handle_render(args.contract, args.environment)
|
|
35
|
+
if args.command == "sources":
|
|
36
|
+
print(json.dumps(list(list_fabric_source_support()), indent=2, sort_keys=True))
|
|
37
|
+
return 0
|
|
38
|
+
return 2
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _handle_plan(contract_path: Path, environment_path: Path | None) -> int:
|
|
42
|
+
result = plan_fabric_contract(_load_yaml(contract_path), environment=_load_optional_yaml(environment_path))
|
|
43
|
+
print(json.dumps(_planning_payload(result), indent=2, sort_keys=True))
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _handle_render(contract_path: Path, environment_path: Path | None) -> int:
|
|
48
|
+
artifacts = render_fabric_contract(_load_yaml(contract_path), environment=_load_optional_yaml(environment_path))
|
|
49
|
+
print(json.dumps(artifacts.artifacts, indent=2, sort_keys=True))
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _load_optional_yaml(path: Path | None) -> dict[str, Any] | None:
|
|
54
|
+
return None if path is None else _load_yaml(path)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _load_yaml(path: Path) -> dict[str, Any]:
|
|
58
|
+
payload = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
59
|
+
if not isinstance(payload, dict):
|
|
60
|
+
raise ValueError(f"Expected YAML object in {path}")
|
|
61
|
+
return payload
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _planning_payload(result) -> dict[str, object]:
|
|
65
|
+
return {
|
|
66
|
+
"status": result.status,
|
|
67
|
+
"blockers": [{"code": item.code, "message": item.message} for item in result.blockers],
|
|
68
|
+
"warnings": [{"code": item.code, "message": item.message} for item in result.warnings],
|
|
69
|
+
"plan": None
|
|
70
|
+
if result.plan is None
|
|
71
|
+
else {
|
|
72
|
+
"platform": result.plan.platform,
|
|
73
|
+
"evidence_required": result.plan.evidence_required,
|
|
74
|
+
"steps": [{"name": step.name, "intent": step.intent} for step in result.plan.steps],
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__": # pragma: no cover
|
|
80
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Fabric planning diagnostics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contractforge_core.planner import PlanningBlocker, PlanningWarning
|
|
6
|
+
from contractforge_core.semantic import SemanticContract
|
|
7
|
+
from contractforge_fabric.sources import classify_fabric_source
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def unsupported_source_blockers(contract: SemanticContract) -> tuple[PlanningBlocker, ...]:
|
|
11
|
+
classification = classify_fabric_source(contract.source.raw)
|
|
12
|
+
if classification.status != "UNSUPPORTED":
|
|
13
|
+
return ()
|
|
14
|
+
return (
|
|
15
|
+
PlanningBlocker(
|
|
16
|
+
code="FABRIC_UNSUPPORTED_SOURCE",
|
|
17
|
+
message=f"Fabric Lakehouse adapter has no declared source mapping for `{classification.source_type}`.",
|
|
18
|
+
),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def fabric_planning_warnings(contract: SemanticContract) -> tuple[PlanningWarning, ...]:
|
|
23
|
+
warnings: list[PlanningWarning] = []
|
|
24
|
+
classification = classify_fabric_source(contract.source.raw)
|
|
25
|
+
if classification.status in {"SUPPORTED_WITH_WARNINGS", "REVIEW_REQUIRED"}:
|
|
26
|
+
warnings.append(
|
|
27
|
+
PlanningWarning(
|
|
28
|
+
code="FABRIC_SOURCE_REVIEW",
|
|
29
|
+
message=f"`{classification.source_type}` maps to {classification.native_mapping or 'Fabric review'}: {classification.note}",
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
warnings.append(
|
|
33
|
+
PlanningWarning(
|
|
34
|
+
code="FABRIC_RENDER_ONLY",
|
|
35
|
+
message="The Fabric adapter currently renders review artifacts only; runtime execution is not yet implemented.",
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
return tuple(warnings)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Fabric adapter environment binding."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class FabricEnvironment:
|
|
11
|
+
"""Adapter-owned interpretation of the core environment contract."""
|
|
12
|
+
|
|
13
|
+
workspace_id: str | None = None
|
|
14
|
+
workspace_name: str | None = None
|
|
15
|
+
lakehouse_id: str | None = None
|
|
16
|
+
lakehouse_name: str | None = None
|
|
17
|
+
warehouse_id: str | None = None
|
|
18
|
+
warehouse_name: str | None = None
|
|
19
|
+
evidence_lakehouse: str | None = None
|
|
20
|
+
evidence_schema: str | None = None
|
|
21
|
+
artifact_uri: str | None = None
|
|
22
|
+
runtime_kind: str | None = None
|
|
23
|
+
notebook_id: str | None = None
|
|
24
|
+
pipeline_id: str | None = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_contract(cls, environment: dict[str, Any] | None = None) -> "FabricEnvironment":
|
|
28
|
+
if not environment:
|
|
29
|
+
return cls()
|
|
30
|
+
evidence = environment.get("evidence") if isinstance(environment.get("evidence"), dict) else {}
|
|
31
|
+
artifacts = environment.get("artifacts") if isinstance(environment.get("artifacts"), dict) else {}
|
|
32
|
+
runtime = environment.get("runtime") if isinstance(environment.get("runtime"), dict) else {}
|
|
33
|
+
parameters = environment.get("parameters") if isinstance(environment.get("parameters"), dict) else {}
|
|
34
|
+
fabric = parameters.get("fabric") if isinstance(parameters.get("fabric"), dict) else {}
|
|
35
|
+
return cls(
|
|
36
|
+
workspace_id=fabric.get("workspace_id"),
|
|
37
|
+
workspace_name=fabric.get("workspace_name"),
|
|
38
|
+
lakehouse_id=fabric.get("lakehouse_id"),
|
|
39
|
+
lakehouse_name=fabric.get("lakehouse_name"),
|
|
40
|
+
warehouse_id=fabric.get("warehouse_id"),
|
|
41
|
+
warehouse_name=fabric.get("warehouse_name"),
|
|
42
|
+
evidence_lakehouse=evidence.get("lakehouse") or evidence.get("database"),
|
|
43
|
+
evidence_schema=evidence.get("schema"),
|
|
44
|
+
artifact_uri=artifacts.get("uri"),
|
|
45
|
+
runtime_kind=runtime.get("kind"),
|
|
46
|
+
notebook_id=fabric.get("notebook_id"),
|
|
47
|
+
pipeline_id=fabric.get("pipeline_id"),
|
|
48
|
+
)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Render Fabric planning review artifacts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from contractforge_core.adapters import RenderedArtifacts
|
|
9
|
+
from contractforge_core.planner import ExecutionPlan, PlanningResult
|
|
10
|
+
from contractforge_core.semantic import SemanticContract
|
|
11
|
+
from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE, fabric_lakehouse_capabilities
|
|
12
|
+
from contractforge_fabric.environment import FabricEnvironment
|
|
13
|
+
from contractforge_fabric.sources import list_fabric_source_support
|
|
14
|
+
|
|
15
|
+
_PUBLIC_REVIEW_REQUIRED_SEMANTICS = {
|
|
16
|
+
"scd2_historical": "historical",
|
|
17
|
+
"snapshot_soft_delete": "snapshot_reconcile_soft_delete",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def render_fabric_review_artifacts(
|
|
22
|
+
*,
|
|
23
|
+
plan: ExecutionPlan | None,
|
|
24
|
+
planning: PlanningResult | None,
|
|
25
|
+
contract: SemanticContract | None = None,
|
|
26
|
+
raw_contract: dict[str, Any] | None = None,
|
|
27
|
+
environment: FabricEnvironment | None = None,
|
|
28
|
+
) -> RenderedArtifacts:
|
|
29
|
+
env = environment or FabricEnvironment()
|
|
30
|
+
prefix = _artifact_prefix(contract, plan)
|
|
31
|
+
artifacts = {
|
|
32
|
+
f"{prefix}.fabric.review.md": _planning_markdown(
|
|
33
|
+
plan=plan,
|
|
34
|
+
planning=planning,
|
|
35
|
+
contract=contract,
|
|
36
|
+
environment=env,
|
|
37
|
+
),
|
|
38
|
+
f"{prefix}.fabric.capabilities.json": _capabilities_json(plan=plan, planning=planning, environment=env),
|
|
39
|
+
f"{prefix}.fabric.source_support.json": json.dumps(list(list_fabric_source_support()), indent=2, sort_keys=True),
|
|
40
|
+
f"{prefix}.fabric.runtime.todo.md": _runtime_todo(contract=contract, environment=env),
|
|
41
|
+
}
|
|
42
|
+
if raw_contract is not None:
|
|
43
|
+
artifacts[f"{prefix}.fabric.contract.json"] = json.dumps(raw_contract, indent=2, sort_keys=True)
|
|
44
|
+
manifest_name = f"{prefix}.fabric.manifest.json"
|
|
45
|
+
artifacts[manifest_name] = _manifest_json(
|
|
46
|
+
plan=plan,
|
|
47
|
+
planning=planning,
|
|
48
|
+
artifacts=artifacts,
|
|
49
|
+
manifest_name=manifest_name,
|
|
50
|
+
)
|
|
51
|
+
return RenderedArtifacts(artifacts=artifacts)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _artifact_prefix(contract: SemanticContract | None, plan: ExecutionPlan | None) -> str:
|
|
55
|
+
if contract is None:
|
|
56
|
+
return (plan.platform if plan else FABRIC_SUBTARGET_LAKEHOUSE).replace(".", "_")
|
|
57
|
+
namespace = (contract.target.namespace or "default").replace(".", "_")
|
|
58
|
+
return f"{namespace}_{contract.target.name}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _capabilities_json(
|
|
62
|
+
*,
|
|
63
|
+
plan: ExecutionPlan | None,
|
|
64
|
+
planning: PlanningResult | None,
|
|
65
|
+
environment: FabricEnvironment,
|
|
66
|
+
) -> str:
|
|
67
|
+
capabilities = fabric_lakehouse_capabilities()
|
|
68
|
+
payload = {
|
|
69
|
+
"adapter": "fabric",
|
|
70
|
+
"subtarget": plan.platform if plan else FABRIC_SUBTARGET_LAKEHOUSE,
|
|
71
|
+
"planning_status": planning.status if planning else None,
|
|
72
|
+
"supports": {
|
|
73
|
+
"append": capabilities.supports_append,
|
|
74
|
+
"overwrite": capabilities.supports_overwrite,
|
|
75
|
+
"upsert": capabilities.supports_merge,
|
|
76
|
+
"hash_diff_upsert": capabilities.supports_hash_diff,
|
|
77
|
+
"historical": capabilities.supports_scd2,
|
|
78
|
+
"snapshot_reconcile_soft_delete": capabilities.supports_snapshot_soft_delete,
|
|
79
|
+
"schema_evolution": capabilities.supports_schema_evolution,
|
|
80
|
+
"row_filters": capabilities.supports_row_filters,
|
|
81
|
+
"column_masks": capabilities.supports_column_masks,
|
|
82
|
+
"available_now_streaming": capabilities.supports_available_now_streaming,
|
|
83
|
+
"shape": capabilities.supports_shape,
|
|
84
|
+
"transform": capabilities.supports_transform,
|
|
85
|
+
"expression_quality": capabilities.supports_expression_quality,
|
|
86
|
+
},
|
|
87
|
+
"evidence": {
|
|
88
|
+
"stores": list(capabilities.evidence_stores),
|
|
89
|
+
"lakehouse": environment.evidence_lakehouse,
|
|
90
|
+
"schema": environment.evidence_schema,
|
|
91
|
+
},
|
|
92
|
+
"runtime": {
|
|
93
|
+
"status": "render_only",
|
|
94
|
+
"workspace_id": environment.workspace_id,
|
|
95
|
+
"workspace_name": environment.workspace_name,
|
|
96
|
+
"lakehouse_id": environment.lakehouse_id,
|
|
97
|
+
"lakehouse_name": environment.lakehouse_name,
|
|
98
|
+
"warehouse_id": environment.warehouse_id,
|
|
99
|
+
"warehouse_name": environment.warehouse_name,
|
|
100
|
+
"artifact_uri": environment.artifact_uri,
|
|
101
|
+
"notebook_id": environment.notebook_id,
|
|
102
|
+
"pipeline_id": environment.pipeline_id,
|
|
103
|
+
},
|
|
104
|
+
"review_required_semantics": [
|
|
105
|
+
_PUBLIC_REVIEW_REQUIRED_SEMANTICS.get(item, item) for item in capabilities.review_required_semantics
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
return json.dumps(payload, indent=2, sort_keys=True)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _planning_markdown(
|
|
112
|
+
*,
|
|
113
|
+
plan: ExecutionPlan | None,
|
|
114
|
+
planning: PlanningResult | None,
|
|
115
|
+
contract: SemanticContract | None,
|
|
116
|
+
environment: FabricEnvironment,
|
|
117
|
+
) -> str:
|
|
118
|
+
lines = [
|
|
119
|
+
"# Fabric Lakehouse Planning Review",
|
|
120
|
+
"",
|
|
121
|
+
"This artifact summarizes how a ContractForge contract maps to the Microsoft Fabric Lakehouse surface.",
|
|
122
|
+
"The current adapter is render-only: it does not call Fabric REST APIs, submit notebooks or update Data Factory pipelines.",
|
|
123
|
+
"",
|
|
124
|
+
"## Fabric Binding",
|
|
125
|
+
"",
|
|
126
|
+
f"- Workspace: `{environment.workspace_name or environment.workspace_id or 'UNSPECIFIED'}`",
|
|
127
|
+
f"- Lakehouse: `{environment.lakehouse_name or environment.lakehouse_id or 'UNSPECIFIED'}`",
|
|
128
|
+
f"- Warehouse: `{environment.warehouse_name or environment.warehouse_id or 'UNSPECIFIED'}`",
|
|
129
|
+
f"- Evidence: `{_evidence_name(environment)}`",
|
|
130
|
+
"",
|
|
131
|
+
]
|
|
132
|
+
if contract:
|
|
133
|
+
lines.extend(
|
|
134
|
+
[
|
|
135
|
+
"## Contract",
|
|
136
|
+
"",
|
|
137
|
+
f"- Source: `{contract.source.kind}`",
|
|
138
|
+
f"- Target: `{contract.target.namespace or 'default'}.{contract.target.name}`",
|
|
139
|
+
f"- Write mode: `{contract.write.mode}`",
|
|
140
|
+
"",
|
|
141
|
+
]
|
|
142
|
+
)
|
|
143
|
+
if planning:
|
|
144
|
+
lines.extend(["## Planning Result", "", f"- Status: `{planning.status}`", ""])
|
|
145
|
+
if planning.blockers:
|
|
146
|
+
lines.extend(["### Blockers", ""])
|
|
147
|
+
lines.extend(f"- `{blocker.code}`: {blocker.message}" for blocker in planning.blockers)
|
|
148
|
+
lines.append("")
|
|
149
|
+
if planning.warnings:
|
|
150
|
+
lines.extend(["### Warnings", ""])
|
|
151
|
+
lines.extend(f"- `{warning.code}`: {warning.message}" for warning in planning.warnings)
|
|
152
|
+
lines.append("")
|
|
153
|
+
if plan:
|
|
154
|
+
lines.extend(["## Abstract Plan", "", "| Step | Intent |", "| --- | --- |"])
|
|
155
|
+
lines.extend(f"| `{step.name}` | {step.intent} |" for step in plan.steps)
|
|
156
|
+
lines.append("")
|
|
157
|
+
lines.extend(
|
|
158
|
+
[
|
|
159
|
+
"## Expected Fabric Mapping",
|
|
160
|
+
"",
|
|
161
|
+
"| ContractForge intent | Fabric target concept |",
|
|
162
|
+
"| --- | --- |",
|
|
163
|
+
"| Source read | OneLake Files/Tables, Lakehouse/Warehouse SQL endpoint, shortcut, Data Factory activity or notebook step |",
|
|
164
|
+
"| Write target | Lakehouse Delta table or Warehouse table, depending on runtime design |",
|
|
165
|
+
"| Transform | Notebook/Spark or SQL endpoint step |",
|
|
166
|
+
"| Evidence | Lakehouse Delta evidence tables |",
|
|
167
|
+
"| Deployment | Future Fabric REST/CLI/Data Factory pipeline integration |",
|
|
168
|
+
"",
|
|
169
|
+
]
|
|
170
|
+
)
|
|
171
|
+
return "\n".join(lines)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _runtime_todo(*, contract: SemanticContract | None, environment: FabricEnvironment) -> str:
|
|
175
|
+
target = f"{contract.target.namespace or 'default'}.{contract.target.name}" if contract else "UNKNOWN"
|
|
176
|
+
return "\n".join(
|
|
177
|
+
[
|
|
178
|
+
"# Fabric Runtime TODO",
|
|
179
|
+
"",
|
|
180
|
+
"This adapter version is render-only. To mature it, implement and validate:",
|
|
181
|
+
"",
|
|
182
|
+
"- Fabric workspace and item resolution.",
|
|
183
|
+
"- OneLake/Lakehouse table name resolution.",
|
|
184
|
+
"- Notebook or Data Factory pipeline artifact generation.",
|
|
185
|
+
"- Runtime submission through Fabric REST API or Fabric CLI.",
|
|
186
|
+
"- Delta/Lakehouse evidence table DDL and writes.",
|
|
187
|
+
"- End-to-end bronze-to-gold contract execution without workaround code.",
|
|
188
|
+
"",
|
|
189
|
+
f"Target contract: `{target}`",
|
|
190
|
+
f"Configured artifact URI: `{environment.artifact_uri or 'UNSPECIFIED'}`",
|
|
191
|
+
"",
|
|
192
|
+
]
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _manifest_json(
|
|
197
|
+
*,
|
|
198
|
+
plan: ExecutionPlan | None,
|
|
199
|
+
planning: PlanningResult | None,
|
|
200
|
+
artifacts: dict[str, str],
|
|
201
|
+
manifest_name: str,
|
|
202
|
+
) -> str:
|
|
203
|
+
payload = {
|
|
204
|
+
"adapter": "fabric",
|
|
205
|
+
"subtarget": plan.platform if plan else FABRIC_SUBTARGET_LAKEHOUSE,
|
|
206
|
+
"planning_status": planning.status if planning else None,
|
|
207
|
+
"artifact_summary": {
|
|
208
|
+
"mode": "review_bundle",
|
|
209
|
+
"execution_model": "render_only",
|
|
210
|
+
"deployable": False,
|
|
211
|
+
"count": len(artifacts) + 1,
|
|
212
|
+
"bytes": sum(len(body.encode("utf-8")) for body in artifacts.values()),
|
|
213
|
+
},
|
|
214
|
+
"artifacts": sorted(tuple(artifacts) + (manifest_name,)),
|
|
215
|
+
}
|
|
216
|
+
return json.dumps(payload, indent=2, sort_keys=True)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _evidence_name(environment: FabricEnvironment) -> str:
|
|
220
|
+
lakehouse = environment.evidence_lakehouse or environment.lakehouse_name or "CONTRACTFORGE_EVIDENCE_LH"
|
|
221
|
+
schema = environment.evidence_schema or "contractforge"
|
|
222
|
+
return f"{lakehouse}.{schema}"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Fabric source mapping helpers."""
|
|
2
|
+
|
|
3
|
+
from contractforge_fabric.sources.classification import (
|
|
4
|
+
FabricSourceClassification,
|
|
5
|
+
classify_fabric_source,
|
|
6
|
+
is_fabric_source_renderable,
|
|
7
|
+
)
|
|
8
|
+
from contractforge_fabric.sources.support import fabric_source_support, list_fabric_source_support
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"FabricSourceClassification",
|
|
12
|
+
"classify_fabric_source",
|
|
13
|
+
"fabric_source_support",
|
|
14
|
+
"is_fabric_source_renderable",
|
|
15
|
+
"list_fabric_source_support",
|
|
16
|
+
]
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Fabric source connector classification."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
from contractforge_core.connectors import (
|
|
9
|
+
JDBC_CONNECTORS,
|
|
10
|
+
is_available_now_stream_source,
|
|
11
|
+
is_bounded_stream_source,
|
|
12
|
+
is_catalog_source,
|
|
13
|
+
is_delta_share_source,
|
|
14
|
+
is_file_source,
|
|
15
|
+
is_http_file_source,
|
|
16
|
+
is_native_passthrough_source,
|
|
17
|
+
is_rest_api_connector,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
SUPPORTED = "SUPPORTED"
|
|
21
|
+
SUPPORTED_WITH_WARNINGS = "SUPPORTED_WITH_WARNINGS"
|
|
22
|
+
REVIEW_REQUIRED = "REVIEW_REQUIRED"
|
|
23
|
+
UNSUPPORTED = "UNSUPPORTED"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class FabricSourceClassification:
|
|
28
|
+
source_type: str
|
|
29
|
+
status: str
|
|
30
|
+
native_mapping: str | None
|
|
31
|
+
note: str
|
|
32
|
+
renderable: bool
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class _SourceRule:
|
|
37
|
+
matches: Callable[[dict[str, Any]], bool]
|
|
38
|
+
classify: Callable[[dict[str, Any], str], FabricSourceClassification]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def classify_fabric_source(source: dict[str, Any] | str) -> FabricSourceClassification:
|
|
42
|
+
payload = {"type": source} if isinstance(source, str) else dict(source)
|
|
43
|
+
source_type = str(payload.get("connector") or payload.get("type") or "").strip().lower()
|
|
44
|
+
for rule in _CLASSIFICATION_RULES:
|
|
45
|
+
if rule.matches(payload):
|
|
46
|
+
return rule.classify(payload, source_type)
|
|
47
|
+
return FabricSourceClassification(
|
|
48
|
+
source_type=source_type,
|
|
49
|
+
status=UNSUPPORTED,
|
|
50
|
+
native_mapping=None,
|
|
51
|
+
note="No Fabric source mapping is declared for this connector.",
|
|
52
|
+
renderable=False,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_fabric_source_renderable(source: dict[str, Any]) -> bool:
|
|
57
|
+
return classify_fabric_source(source).renderable
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _is_jdbc_source(source: dict[str, Any]) -> bool:
|
|
61
|
+
connector = source.get("connector") or source.get("type")
|
|
62
|
+
return connector in JDBC_CONNECTORS or source.get("type") == "jdbc"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _supported(
|
|
66
|
+
source_type: str,
|
|
67
|
+
*,
|
|
68
|
+
native_mapping: str,
|
|
69
|
+
note: str,
|
|
70
|
+
status: str = SUPPORTED,
|
|
71
|
+
renderable: bool = True,
|
|
72
|
+
) -> FabricSourceClassification:
|
|
73
|
+
return FabricSourceClassification(
|
|
74
|
+
source_type=source_type,
|
|
75
|
+
status=status,
|
|
76
|
+
native_mapping=native_mapping,
|
|
77
|
+
note=note,
|
|
78
|
+
renderable=renderable,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _catalog_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
83
|
+
return _supported(
|
|
84
|
+
source_type,
|
|
85
|
+
native_mapping="Fabric Lakehouse/Warehouse table or SQL endpoint",
|
|
86
|
+
note="Logical refs are resolved to Fabric item/table names by the adapter.",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _file_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
91
|
+
return _supported(
|
|
92
|
+
source_type,
|
|
93
|
+
native_mapping="OneLake Files/Tables path read",
|
|
94
|
+
note="Batch file mapping for Lakehouse runtime artifacts.",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _jdbc_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
99
|
+
return _supported(
|
|
100
|
+
source_type,
|
|
101
|
+
status=REVIEW_REQUIRED,
|
|
102
|
+
native_mapping="Fabric Data Factory pipeline or notebook JDBC read",
|
|
103
|
+
note="Driver, gateway, credential and private-network choices must be reviewed.",
|
|
104
|
+
renderable=False,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _http_file_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
109
|
+
return _supported(
|
|
110
|
+
source_type,
|
|
111
|
+
status=REVIEW_REQUIRED,
|
|
112
|
+
native_mapping="Fabric notebook or Data Factory web activity",
|
|
113
|
+
note="Bounded fetch is portable, but Fabric runtime ownership is not implemented yet.",
|
|
114
|
+
renderable=False,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _rest_api_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
119
|
+
return _supported(
|
|
120
|
+
source_type,
|
|
121
|
+
status=REVIEW_REQUIRED,
|
|
122
|
+
native_mapping="Fabric Data Factory REST copy or notebook fetch",
|
|
123
|
+
note="Generic bounded REST runtime needs explicit Fabric execution design.",
|
|
124
|
+
renderable=False,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _incremental_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
129
|
+
return _supported(
|
|
130
|
+
source_type,
|
|
131
|
+
status=REVIEW_REQUIRED,
|
|
132
|
+
native_mapping="Fabric Data Factory incremental pipeline or notebook checkpoint",
|
|
133
|
+
note="File-discovery and checkpoint semantics require runtime evidence.",
|
|
134
|
+
renderable=False,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _bounded_stream_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
139
|
+
return _supported(
|
|
140
|
+
source_type,
|
|
141
|
+
status=REVIEW_REQUIRED,
|
|
142
|
+
native_mapping="Fabric Real-Time Intelligence or notebook bounded replay",
|
|
143
|
+
note="Continuous and bounded replay semantics are not implemented in v0.",
|
|
144
|
+
renderable=False,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _delta_share_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
149
|
+
return _supported(
|
|
150
|
+
source_type,
|
|
151
|
+
status=SUPPORTED_WITH_WARNINGS,
|
|
152
|
+
native_mapping="Delta Sharing client materialized into OneLake",
|
|
153
|
+
note="Requires runtime dependency and credential review.",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _native_passthrough_classification(_source: dict[str, Any], source_type: str) -> FabricSourceClassification:
|
|
158
|
+
return _supported(
|
|
159
|
+
source_type,
|
|
160
|
+
status=REVIEW_REQUIRED,
|
|
161
|
+
native_mapping="Fabric native connector, shortcut or Data Factory activity",
|
|
162
|
+
note="Native connector handoff must be reviewed per source system.",
|
|
163
|
+
renderable=False,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _is_incremental_files(source: dict[str, Any]) -> bool:
|
|
168
|
+
return str(source.get("type") or "").strip().lower() == "incremental_files"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
_CLASSIFICATION_RULES = (
|
|
172
|
+
_SourceRule(_is_incremental_files, _incremental_classification),
|
|
173
|
+
_SourceRule(_is_jdbc_source, _jdbc_classification),
|
|
174
|
+
_SourceRule(is_catalog_source, _catalog_classification),
|
|
175
|
+
_SourceRule(is_http_file_source, _http_file_classification),
|
|
176
|
+
_SourceRule(is_rest_api_connector, _rest_api_classification),
|
|
177
|
+
_SourceRule(is_available_now_stream_source, _bounded_stream_classification),
|
|
178
|
+
_SourceRule(is_bounded_stream_source, _bounded_stream_classification),
|
|
179
|
+
_SourceRule(is_delta_share_source, _delta_share_classification),
|
|
180
|
+
_SourceRule(is_file_source, _file_classification),
|
|
181
|
+
_SourceRule(is_native_passthrough_source, _native_passthrough_classification),
|
|
182
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Fabric source connector support declarations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from contractforge_core.connectors.registry import CONNECTOR_CATALOG
|
|
8
|
+
from contractforge_fabric.sources.classification import FabricSourceClassification, classify_fabric_source
|
|
9
|
+
|
|
10
|
+
_NON_RUNTIME_SOURCE_TYPES = frozenset({"connection"})
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def fabric_source_support(source: dict[str, Any] | str) -> dict[str, Any]:
|
|
14
|
+
"""Return Fabric Lakehouse support metadata for a source connector."""
|
|
15
|
+
|
|
16
|
+
return _entry(classify_fabric_source(source))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def list_fabric_source_support() -> tuple[dict[str, Any], ...]:
|
|
20
|
+
sources = tuple(name for name in CONNECTOR_CATALOG if name not in _NON_RUNTIME_SOURCE_TYPES)
|
|
21
|
+
return tuple(fabric_source_support(source) for source in sources)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _entry(classification: FabricSourceClassification) -> dict[str, Any]:
|
|
25
|
+
entry: dict[str, Any] = {
|
|
26
|
+
"adapter": "fabric",
|
|
27
|
+
"source_type": classification.source_type,
|
|
28
|
+
"status": classification.status,
|
|
29
|
+
"note": classification.note,
|
|
30
|
+
}
|
|
31
|
+
if classification.native_mapping:
|
|
32
|
+
entry["native_mapping"] = classification.native_mapping
|
|
33
|
+
return entry
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Fabric adapter subtarget registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from contractforge_fabric.adapter import FabricAdapter
|
|
8
|
+
from contractforge_fabric.capabilities import FABRIC_SUBTARGET_LAKEHOUSE
|
|
9
|
+
|
|
10
|
+
_ADAPTER_FACTORIES: dict[str, Callable[..., FabricAdapter]] = {
|
|
11
|
+
FABRIC_SUBTARGET_LAKEHOUSE: FabricAdapter.lakehouse,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def adapter_for_subtarget(subtarget: str, *, environment: dict[str, Any] | None = None) -> FabricAdapter:
|
|
16
|
+
try:
|
|
17
|
+
return _ADAPTER_FACTORIES[subtarget](environment=environment)
|
|
18
|
+
except KeyError as exc:
|
|
19
|
+
raise ValueError(f"Unsupported Fabric adapter subtarget: {subtarget}") from exc
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def validate_fabric_subtarget(subtarget: str) -> None:
|
|
23
|
+
adapter_for_subtarget(subtarget)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def list_fabric_subtargets() -> tuple[str, ...]:
|
|
27
|
+
return tuple(_ADAPTER_FACTORIES)
|