fabric-dw 0.1.dev77__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 (125) hide show
  1. fabric_dw-0.1.dev77/.devcontainer/devcontainer.json +27 -0
  2. fabric_dw-0.1.dev77/.env.example +8 -0
  3. fabric_dw-0.1.dev77/.github/CODEOWNERS +1 -0
  4. fabric_dw-0.1.dev77/.github/ISSUE_TEMPLATE/bug_report.yml +33 -0
  5. fabric_dw-0.1.dev77/.github/ISSUE_TEMPLATE/feature_request.yml +30 -0
  6. fabric_dw-0.1.dev77/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  7. fabric_dw-0.1.dev77/.github/dependabot.yml +25 -0
  8. fabric_dw-0.1.dev77/.github/release-drafter.yml +28 -0
  9. fabric_dw-0.1.dev77/.github/workflows/ci.yml +73 -0
  10. fabric_dw-0.1.dev77/.github/workflows/docker.yml +58 -0
  11. fabric_dw-0.1.dev77/.github/workflows/integration.yml +189 -0
  12. fabric_dw-0.1.dev77/.github/workflows/publish.yml +44 -0
  13. fabric_dw-0.1.dev77/.github/workflows/release-drafter.yml +16 -0
  14. fabric_dw-0.1.dev77/.github/workflows/security.yml +38 -0
  15. fabric_dw-0.1.dev77/.gitignore +50 -0
  16. fabric_dw-0.1.dev77/.python-version +1 -0
  17. fabric_dw-0.1.dev77/CODE_OF_CONDUCT.md +83 -0
  18. fabric_dw-0.1.dev77/CONTRIBUTING.md +92 -0
  19. fabric_dw-0.1.dev77/Dockerfile +18 -0
  20. fabric_dw-0.1.dev77/LICENSE +21 -0
  21. fabric_dw-0.1.dev77/PKG-INFO +143 -0
  22. fabric_dw-0.1.dev77/README.md +92 -0
  23. fabric_dw-0.1.dev77/SECURITY.md +16 -0
  24. fabric_dw-0.1.dev77/docs/assets/logo.svg +17 -0
  25. fabric_dw-0.1.dev77/docs/authentication.md +112 -0
  26. fabric_dw-0.1.dev77/docs/cli.md +702 -0
  27. fabric_dw-0.1.dev77/docs/code-of-conduct.md +5 -0
  28. fabric_dw-0.1.dev77/docs/completion.md +73 -0
  29. fabric_dw-0.1.dev77/docs/contributing.md +5 -0
  30. fabric_dw-0.1.dev77/docs/index.md +31 -0
  31. fabric_dw-0.1.dev77/docs/install.md +45 -0
  32. fabric_dw-0.1.dev77/docs/license.md +5 -0
  33. fabric_dw-0.1.dev77/docs/mcp.md +325 -0
  34. fabric_dw-0.1.dev77/docs/security.md +5 -0
  35. fabric_dw-0.1.dev77/docs/troubleshooting.md +179 -0
  36. fabric_dw-0.1.dev77/docs_build/cloudflare_pages.sh +9 -0
  37. fabric_dw-0.1.dev77/justfile +19 -0
  38. fabric_dw-0.1.dev77/overrides/partials/integrations/analytics/custom.html +18 -0
  39. fabric_dw-0.1.dev77/pyproject.toml +144 -0
  40. fabric_dw-0.1.dev77/src/fabric_dw/__init__.py +5 -0
  41. fabric_dw-0.1.dev77/src/fabric_dw/_version.py +24 -0
  42. fabric_dw-0.1.dev77/src/fabric_dw/auth.py +112 -0
  43. fabric_dw-0.1.dev77/src/fabric_dw/cache.py +259 -0
  44. fabric_dw-0.1.dev77/src/fabric_dw/cli/__init__.py +10 -0
  45. fabric_dw-0.1.dev77/src/fabric_dw/cli/_context.py +29 -0
  46. fabric_dw-0.1.dev77/src/fabric_dw/cli/_main.py +82 -0
  47. fabric_dw-0.1.dev77/src/fabric_dw/cli/_render.py +104 -0
  48. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/__init__.py +0 -0
  49. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/_utils.py +90 -0
  50. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/audit.py +144 -0
  51. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/cache.py +26 -0
  52. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/completion.py +94 -0
  53. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/config.py +120 -0
  54. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/endpoints.py +112 -0
  55. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/queries.py +109 -0
  56. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/snapshots.py +228 -0
  57. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/warehouses.py +236 -0
  58. fabric_dw-0.1.dev77/src/fabric_dw/cli/commands/workspaces.py +126 -0
  59. fabric_dw-0.1.dev77/src/fabric_dw/config.py +159 -0
  60. fabric_dw-0.1.dev77/src/fabric_dw/exceptions.py +34 -0
  61. fabric_dw-0.1.dev77/src/fabric_dw/http_client.py +337 -0
  62. fabric_dw-0.1.dev77/src/fabric_dw/logging.py +90 -0
  63. fabric_dw-0.1.dev77/src/fabric_dw/mcp/__init__.py +13 -0
  64. fabric_dw-0.1.dev77/src/fabric_dw/mcp/server.py +602 -0
  65. fabric_dw-0.1.dev77/src/fabric_dw/models.py +119 -0
  66. fabric_dw-0.1.dev77/src/fabric_dw/py.typed +0 -0
  67. fabric_dw-0.1.dev77/src/fabric_dw/resolver.py +241 -0
  68. fabric_dw-0.1.dev77/src/fabric_dw/services/__init__.py +5 -0
  69. fabric_dw-0.1.dev77/src/fabric_dw/services/audit.py +154 -0
  70. fabric_dw-0.1.dev77/src/fabric_dw/services/ownership.py +40 -0
  71. fabric_dw-0.1.dev77/src/fabric_dw/services/queries.py +121 -0
  72. fabric_dw-0.1.dev77/src/fabric_dw/services/snapshots.py +311 -0
  73. fabric_dw-0.1.dev77/src/fabric_dw/services/sql_endpoints.py +124 -0
  74. fabric_dw-0.1.dev77/src/fabric_dw/services/warehouses.py +238 -0
  75. fabric_dw-0.1.dev77/src/fabric_dw/services/workspaces.py +131 -0
  76. fabric_dw-0.1.dev77/src/fabric_dw/sql.py +193 -0
  77. fabric_dw-0.1.dev77/tests/__init__.py +0 -0
  78. fabric_dw-0.1.dev77/tests/fixtures/__init__.py +0 -0
  79. fabric_dw-0.1.dev77/tests/fixtures/api_payloads.py +273 -0
  80. fabric_dw-0.1.dev77/tests/integration/__init__.py +0 -0
  81. fabric_dw-0.1.dev77/tests/integration/conftest.py +67 -0
  82. fabric_dw-0.1.dev77/tests/integration/test_cross_cutting.py +47 -0
  83. fabric_dw-0.1.dev77/tests/integration/test_services_audit.py +45 -0
  84. fabric_dw-0.1.dev77/tests/integration/test_services_ownership.py +22 -0
  85. fabric_dw-0.1.dev77/tests/integration/test_services_queries.py +18 -0
  86. fabric_dw-0.1.dev77/tests/integration/test_services_snapshots.py +26 -0
  87. fabric_dw-0.1.dev77/tests/integration/test_services_warehouses.py +42 -0
  88. fabric_dw-0.1.dev77/tests/integration/test_services_workspaces.py +45 -0
  89. fabric_dw-0.1.dev77/tests/integration/test_smoke.py +17 -0
  90. fabric_dw-0.1.dev77/tests/unit/__init__.py +0 -0
  91. fabric_dw-0.1.dev77/tests/unit/cli/__init__.py +0 -0
  92. fabric_dw-0.1.dev77/tests/unit/cli/commands/__init__.py +0 -0
  93. fabric_dw-0.1.dev77/tests/unit/cli/commands/test_audit.py +304 -0
  94. fabric_dw-0.1.dev77/tests/unit/cli/commands/test_config.py +162 -0
  95. fabric_dw-0.1.dev77/tests/unit/cli/commands/test_endpoints.py +278 -0
  96. fabric_dw-0.1.dev77/tests/unit/cli/commands/test_queries.py +240 -0
  97. fabric_dw-0.1.dev77/tests/unit/cli/commands/test_snapshots.py +424 -0
  98. fabric_dw-0.1.dev77/tests/unit/cli/commands/test_warehouses.py +414 -0
  99. fabric_dw-0.1.dev77/tests/unit/cli/commands/test_workspaces.py +286 -0
  100. fabric_dw-0.1.dev77/tests/unit/cli/test_cache.py +74 -0
  101. fabric_dw-0.1.dev77/tests/unit/cli/test_completion.py +62 -0
  102. fabric_dw-0.1.dev77/tests/unit/cli/test_main.py +85 -0
  103. fabric_dw-0.1.dev77/tests/unit/cli/test_render.py +100 -0
  104. fabric_dw-0.1.dev77/tests/unit/mcp/__init__.py +0 -0
  105. fabric_dw-0.1.dev77/tests/unit/mcp/test_server.py +754 -0
  106. fabric_dw-0.1.dev77/tests/unit/services/__init__.py +0 -0
  107. fabric_dw-0.1.dev77/tests/unit/services/test_audit.py +263 -0
  108. fabric_dw-0.1.dev77/tests/unit/services/test_ownership.py +109 -0
  109. fabric_dw-0.1.dev77/tests/unit/services/test_queries.py +395 -0
  110. fabric_dw-0.1.dev77/tests/unit/services/test_snapshots.py +601 -0
  111. fabric_dw-0.1.dev77/tests/unit/services/test_sql_endpoints.py +423 -0
  112. fabric_dw-0.1.dev77/tests/unit/services/test_warehouses.py +688 -0
  113. fabric_dw-0.1.dev77/tests/unit/services/test_workspaces.py +248 -0
  114. fabric_dw-0.1.dev77/tests/unit/test_auth.py +242 -0
  115. fabric_dw-0.1.dev77/tests/unit/test_cache.py +367 -0
  116. fabric_dw-0.1.dev77/tests/unit/test_config.py +133 -0
  117. fabric_dw-0.1.dev77/tests/unit/test_http_client.py +443 -0
  118. fabric_dw-0.1.dev77/tests/unit/test_logging.py +74 -0
  119. fabric_dw-0.1.dev77/tests/unit/test_models.py +250 -0
  120. fabric_dw-0.1.dev77/tests/unit/test_resolver.py +549 -0
  121. fabric_dw-0.1.dev77/tests/unit/test_smoke.py +6 -0
  122. fabric_dw-0.1.dev77/tests/unit/test_sql.py +289 -0
  123. fabric_dw-0.1.dev77/uv.lock +1868 -0
  124. fabric_dw-0.1.dev77/wrangler.toml +3 -0
  125. fabric_dw-0.1.dev77/zensical.toml +133 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "fabric-dw",
3
+ "image": "ghcr.io/astral-sh/uv:python3.13-bookworm",
4
+ "features": {
5
+ "ghcr.io/devcontainers/features/azure-cli:1": {},
6
+ "ghcr.io/devcontainers/features/github-cli:1": {},
7
+ "ghcr.io/devcontainers/features/common-utils:2": { "username": "vscode" }
8
+ },
9
+ "postCreateCommand": "uv sync",
10
+ "customizations": {
11
+ "vscode": {
12
+ "extensions": [
13
+ "charliermarsh.ruff",
14
+ "ms-python.python",
15
+ "ms-python.mypy-type-checker",
16
+ "tamasfe.even-better-toml",
17
+ "GitHub.copilot",
18
+ "GitHub.copilot-chat"
19
+ ],
20
+ "settings": {
21
+ "python.defaultInterpreterPath": "/usr/local/bin/python",
22
+ "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" }
23
+ }
24
+ }
25
+ },
26
+ "remoteUser": "vscode"
27
+ }
@@ -0,0 +1,8 @@
1
+ FABRIC_TEST_WORKSPACE_ID=
2
+ FABRIC_AUTH=default
3
+ # AZURE_TENANT_ID=
4
+ # AZURE_CLIENT_ID=
5
+ # AZURE_CLIENT_SECRET=
6
+ # Override the shared multi-tenant app used for interactive browser sign-in:
7
+ # FABRIC_INTERACTIVE_CLIENT_ID=f666e5ee-2149-4c6a-87eb-13c9e1fdc70d
8
+ # FABRIC_INTERACTIVE_TENANT_ID=
@@ -0,0 +1 @@
1
+ * @sdebruyn
@@ -0,0 +1,33 @@
1
+ name: Bug report
2
+ description: Report a bug or unexpected behavior
3
+ title: "bug: "
4
+ labels: [bug]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for taking the time to report a bug!
10
+ - type: textarea
11
+ id: description
12
+ attributes:
13
+ label: Description
14
+ description: What happened? What did you expect to happen?
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: steps
19
+ attributes:
20
+ label: Steps to reproduce
21
+ description: How can we reproduce the issue?
22
+ placeholder: |
23
+ 1. Run `fabric-dw ...`
24
+ 2. See error
25
+ validations:
26
+ required: true
27
+ - type: textarea
28
+ id: environment
29
+ attributes:
30
+ label: Environment
31
+ description: "Python version, OS, package version"
32
+ validations:
33
+ required: false
@@ -0,0 +1,30 @@
1
+ name: Feature request
2
+ description: Suggest a new feature or enhancement
3
+ title: "feat: "
4
+ labels: [enhancement]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for suggesting a feature!
10
+ - type: textarea
11
+ id: problem
12
+ attributes:
13
+ label: Problem or motivation
14
+ description: What problem does this feature solve?
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: solution
19
+ attributes:
20
+ label: Proposed solution
21
+ description: Describe the feature you'd like to see.
22
+ validations:
23
+ required: true
24
+ - type: textarea
25
+ id: alternatives
26
+ attributes:
27
+ label: Alternatives considered
28
+ description: Any alternative solutions or workarounds you've considered?
29
+ validations:
30
+ required: false
@@ -0,0 +1,14 @@
1
+ ## Summary
2
+
3
+ <!-- Describe what this PR does and why. -->
4
+
5
+ ## Test plan
6
+
7
+ - [ ] Pre-commit hooks pass (`uv run pre-commit run --all-files`)
8
+ - [ ] Unit tests pass (`uv run pytest tests/unit`)
9
+ - [ ] Integration tests pass where applicable (`uv run pytest tests/integration`)
10
+ - [ ] Manual testing steps (if any):
11
+
12
+ ## Linked issue
13
+
14
+ Closes #
@@ -0,0 +1,25 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ groups:
8
+ patch-and-minor:
9
+ patterns:
10
+ - "*"
11
+ update-types:
12
+ - minor
13
+ - patch
14
+
15
+ - package-ecosystem: github-actions
16
+ directory: /
17
+ schedule:
18
+ interval: weekly
19
+ groups:
20
+ patch-and-minor:
21
+ patterns:
22
+ - "*"
23
+ update-types:
24
+ - minor
25
+ - patch
@@ -0,0 +1,28 @@
1
+ name-template: 'v$RESOLVED_VERSION'
2
+ tag-template: 'v$RESOLVED_VERSION'
3
+ categories:
4
+ - title: 'Features'
5
+ labels: ['type:feature']
6
+ commitish:
7
+ - "feat"
8
+ - title: 'Fixes'
9
+ labels: ['type:fix', 'bug']
10
+ commitish:
11
+ - "fix"
12
+ - title: 'Documentation'
13
+ labels: ['area:docs', 'area:docs-site']
14
+ commitish:
15
+ - "docs"
16
+ - title: 'CI / Tooling'
17
+ labels: ['area:ci', 'area:bootstrap']
18
+ commitish:
19
+ - "ci"
20
+ - "chore"
21
+ - title: 'Other'
22
+ change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
23
+ change-title-escapes: '\<*_&'
24
+ version-resolver:
25
+ default: patch
26
+ template: |
27
+ ## Changes
28
+ $CHANGES
@@ -0,0 +1,73 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ lint:
18
+ name: Lint
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v6
22
+ with:
23
+ fetch-depth: 0
24
+ - uses: astral-sh/setup-uv@v8.2.0
25
+ with:
26
+ enable-cache: true
27
+ - uses: actions/setup-python@v6
28
+ with:
29
+ python-version: "3.11"
30
+ - run: uv sync --frozen
31
+ - run: uv run ruff check .
32
+ - run: uv run ruff format --check .
33
+
34
+ type:
35
+ name: Type check
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v6
39
+ with:
40
+ fetch-depth: 0
41
+ - uses: astral-sh/setup-uv@v8.2.0
42
+ with:
43
+ enable-cache: true
44
+ - uses: actions/setup-python@v6
45
+ with:
46
+ python-version: "3.11"
47
+ - run: uv sync --frozen
48
+ - run: uv run mypy src tests
49
+
50
+ unit:
51
+ name: Unit tests
52
+ runs-on: ubuntu-latest
53
+ strategy:
54
+ matrix:
55
+ python-version: ["3.11", "3.12", "3.13"]
56
+ steps:
57
+ - uses: actions/checkout@v6
58
+ with:
59
+ fetch-depth: 0
60
+ - uses: astral-sh/setup-uv@v8.2.0
61
+ with:
62
+ enable-cache: true
63
+ - uses: actions/setup-python@v6
64
+ with:
65
+ python-version: ${{ matrix.python-version }}
66
+ - run: uv sync --frozen
67
+ - run: uv run pytest tests/unit --cov=fabric_dw --cov-report=xml
68
+ - name: Coverage report
69
+ run: uv run coverage report
70
+ - uses: actions/upload-artifact@v7
71
+ with:
72
+ name: coverage-${{ matrix.python-version }}
73
+ path: coverage.xml
@@ -0,0 +1,58 @@
1
+ name: Docker
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v[0-9]+.[0-9]+.[0-9]+"]
7
+
8
+ permissions:
9
+ contents: read
10
+ packages: write
11
+ id-token: write
12
+
13
+ jobs:
14
+ build-and-push:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - uses: astral-sh/setup-uv@v8.2.0
22
+ with:
23
+ enable-cache: true
24
+
25
+ - uses: actions/setup-python@v6
26
+ with:
27
+ python-version: "3.13"
28
+
29
+ - run: uv sync --frozen --no-dev
30
+
31
+ - name: Compute Docker tags
32
+ id: docker_tags
33
+ run: |
34
+ VERSION=$(uv run python -c "import fabric_dw; print(fabric_dw.__version__)")
35
+ if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then
36
+ echo "tags=ghcr.io/sdebruyn/fabric-dw:$VERSION,ghcr.io/sdebruyn/fabric-dw:latest" >> $GITHUB_OUTPUT
37
+ else
38
+ echo "tags=ghcr.io/sdebruyn/fabric-dw:$VERSION,ghcr.io/sdebruyn/fabric-dw:main" >> $GITHUB_OUTPUT
39
+ fi
40
+
41
+ - uses: docker/setup-qemu-action@v3
42
+ - uses: docker/setup-buildx-action@v3
43
+
44
+ - uses: docker/login-action@v3
45
+ with:
46
+ registry: ghcr.io
47
+ username: ${{ github.actor }}
48
+ password: ${{ secrets.GITHUB_TOKEN }}
49
+
50
+ - name: Build & push
51
+ uses: docker/build-push-action@v6
52
+ with:
53
+ context: .
54
+ platforms: linux/amd64,linux/arm64
55
+ push: true
56
+ tags: ${{ steps.docker_tags.outputs.tags }}
57
+ cache-from: type=gha
58
+ cache-to: type=gha,mode=max
@@ -0,0 +1,189 @@
1
+ name: Integration tests
2
+
3
+ on:
4
+ pull_request:
5
+ types: [labeled, synchronize]
6
+ push:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ id-token: write
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: integration-tests
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ integration:
19
+ if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'integration') }}
20
+ runs-on: ubuntu-latest
21
+ environment: integration
22
+ timeout-minutes: 45
23
+ steps:
24
+ - uses: actions/checkout@v6
25
+ with:
26
+ fetch-depth: 0
27
+
28
+ - uses: azure/login@v2
29
+ with:
30
+ client-id: ${{ secrets.AZURE_CLIENT_ID }}
31
+ tenant-id: ${{ secrets.AZURE_TENANT_ID }}
32
+ subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
33
+
34
+ - name: Resume Fabric capacity
35
+ run: |
36
+ set -e
37
+ CAPACITY_ID="${{ vars.FABRIC_TEST_CAPACITY_ID }}"
38
+
39
+ # Wait for the capacity to leave any transitional state, up to 5 min
40
+ for i in {1..30}; do
41
+ STATE=$(az resource show --ids "$CAPACITY_ID" --query 'properties.state' -o tsv 2>/dev/null || echo "Unknown")
42
+ echo "Pre-resume state: $STATE"
43
+ case "$STATE" in
44
+ Active) echo "Already Active, skipping resume"; exit 0 ;;
45
+ Paused) break ;;
46
+ *) sleep 10 ;;
47
+ esac
48
+ done
49
+
50
+ # Issue resume, but tolerate transient BadRequest
51
+ for attempt in {1..6}; do
52
+ if az resource invoke-action --ids "$CAPACITY_ID" --action resume; then
53
+ break
54
+ fi
55
+ echo "Resume attempt $attempt failed (likely transient), waiting 15s..."
56
+ sleep 15
57
+ done
58
+
59
+ # Poll until Active, max 5 min
60
+ for i in {1..30}; do
61
+ STATE=$(az resource show --ids "$CAPACITY_ID" --query 'properties.state' -o tsv)
62
+ echo "Post-resume state: $STATE"
63
+ [[ "$STATE" == "Active" ]] && exit 0
64
+ sleep 10
65
+ done
66
+
67
+ echo "Capacity did not reach Active state within 5 minutes" >&2
68
+ exit 1
69
+
70
+ - uses: astral-sh/setup-uv@v8.2.0
71
+ with:
72
+ enable-cache: true
73
+
74
+ - uses: actions/setup-python@v6
75
+ with:
76
+ python-version: "3.13"
77
+
78
+ - run: uv sync --frozen
79
+
80
+ - name: Pre-test sweep of stale pytest-* items
81
+ env:
82
+ FABRIC_TEST_WORKSPACE_ID: ${{ secrets.FABRIC_TEST_WORKSPACE_ID }}
83
+ FABRIC_AUTH: default
84
+ run: |
85
+ uv run python -c "
86
+ import anyio, os
87
+ from datetime import datetime, timedelta, UTC
88
+ from fabric_dw.auth import get_credential
89
+ from fabric_dw.http_client import FabricHttpClient
90
+ from fabric_dw.services import warehouses, snapshots
91
+
92
+ async def main() -> None:
93
+ ws = os.environ['FABRIC_TEST_WORKSPACE_ID']
94
+ cutoff = datetime.now(UTC) - timedelta(hours=1)
95
+ cred = get_credential()
96
+ async with FabricHttpClient(cred) as http:
97
+ items = await warehouses.list_warehouses(http, ws)
98
+ for w in items:
99
+ if w.name.startswith('pytest-') and (w.created_date is None or w.created_date < cutoff):
100
+ print(f'Sweeping {w.kind} {w.name} ({w.id})')
101
+ try:
102
+ if w.kind == 'WarehouseSnapshot':
103
+ await snapshots.delete(http, ws, w.id)
104
+ else:
105
+ await warehouses.delete(http, ws, w.id)
106
+ except Exception as exc:
107
+ print(f' skip: {exc}')
108
+
109
+ anyio.run(main)
110
+ "
111
+
112
+ - name: Run integration tests
113
+ env:
114
+ FABRIC_TEST_WORKSPACE_ID: ${{ secrets.FABRIC_TEST_WORKSPACE_ID }}
115
+ FABRIC_AUTH: default
116
+ run: uv run pytest tests/integration -m integration -v
117
+
118
+ - name: Post-test sweep of pytest-* items
119
+ if: always()
120
+ env:
121
+ FABRIC_TEST_WORKSPACE_ID: ${{ secrets.FABRIC_TEST_WORKSPACE_ID }}
122
+ FABRIC_AUTH: default
123
+ run: |
124
+ uv run python -c "
125
+ import anyio, os
126
+ from fabric_dw.auth import get_credential
127
+ from fabric_dw.http_client import FabricHttpClient
128
+ from fabric_dw.services import warehouses, snapshots
129
+
130
+ async def main() -> None:
131
+ ws = os.environ['FABRIC_TEST_WORKSPACE_ID']
132
+ cred = get_credential()
133
+ failures = []
134
+ async with FabricHttpClient(cred) as http:
135
+ items = await warehouses.list_warehouses(http, ws)
136
+ for w in items:
137
+ if w.name.startswith('pytest-'):
138
+ print(f'Deleting {w.kind} {w.name} ({w.id})')
139
+ try:
140
+ if w.kind == 'WarehouseSnapshot':
141
+ await snapshots.delete(http, ws, w.id)
142
+ else:
143
+ await warehouses.delete(http, ws, w.id)
144
+ except Exception as exc:
145
+ failures.append((w.name, str(exc)))
146
+ if failures:
147
+ for name, err in failures:
148
+ print(f'FAIL: {name}: {err}')
149
+ raise SystemExit(1)
150
+
151
+ anyio.run(main)
152
+ "
153
+
154
+ - name: Suspend Fabric capacity
155
+ if: always()
156
+ run: |
157
+ set -e
158
+ CAPACITY_ID="${{ vars.FABRIC_TEST_CAPACITY_ID }}"
159
+
160
+ # Wait for the capacity to leave any transitional state, up to 5 min
161
+ for i in {1..30}; do
162
+ STATE=$(az resource show --ids "$CAPACITY_ID" --query 'properties.state' -o tsv 2>/dev/null || echo "Unknown")
163
+ echo "Pre-suspend state: $STATE"
164
+ case "$STATE" in
165
+ Paused) echo "Already Paused, skipping suspend"; exit 0 ;;
166
+ Active) break ;;
167
+ *) sleep 10 ;;
168
+ esac
169
+ done
170
+
171
+ # Issue suspend, but tolerate transient BadRequest
172
+ for attempt in {1..6}; do
173
+ if az resource invoke-action --ids "$CAPACITY_ID" --action suspend; then
174
+ break
175
+ fi
176
+ echo "Suspend attempt $attempt failed (likely transient), waiting 15s..."
177
+ sleep 15
178
+ done
179
+
180
+ # Poll until Paused, max 5 min
181
+ for i in {1..30}; do
182
+ STATE=$(az resource show --ids "$CAPACITY_ID" --query 'properties.state' -o tsv)
183
+ echo "Post-suspend state: $STATE"
184
+ [[ "$STATE" == "Paused" ]] && exit 0
185
+ sleep 10
186
+ done
187
+
188
+ echo "Capacity did not reach Paused state within 5 minutes" >&2
189
+ exit 1
@@ -0,0 +1,44 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v[0-9]+.[0-9]+.[0-9]+"]
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ environment: pypi
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - uses: astral-sh/setup-uv@v8.2.0
22
+ with:
23
+ enable-cache: true
24
+
25
+ - uses: actions/setup-python@v6
26
+ with:
27
+ python-version: "3.13"
28
+
29
+ - run: uv sync --frozen --no-dev
30
+
31
+ - run: uv build
32
+
33
+ - name: Publish to PyPI (OIDC)
34
+ run: uv publish --trusted-publishing always
35
+
36
+ - name: Create GitHub Release (tag only)
37
+ if: github.ref_type == 'tag'
38
+ uses: softprops/action-gh-release@v3
39
+ with:
40
+ files: |
41
+ dist/*.whl
42
+ dist/*.tar.gz
43
+ generate_release_notes: true
44
+ name: ${{ github.ref_name }}
@@ -0,0 +1,16 @@
1
+ name: Release Drafter
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ types: [opened, reopened, synchronize]
7
+ permissions:
8
+ contents: write
9
+ pull-requests: read
10
+ jobs:
11
+ update_release_draft:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: release-drafter/release-drafter@v7
15
+ env:
16
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,38 @@
1
+ name: Security
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: [main]
6
+ permissions:
7
+ contents: read
8
+ pull-requests: read
9
+ jobs:
10
+ bandit:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ with:
15
+ fetch-depth: 0
16
+ - uses: astral-sh/setup-uv@v8.2.0
17
+ with: { enable-cache: true }
18
+ - run: uvx --quiet bandit -r src/ -ll
19
+ dependency-review:
20
+ if: github.event_name == 'pull_request'
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v6
24
+ with:
25
+ fetch-depth: 0
26
+ - uses: actions/dependency-review-action@v5
27
+ with:
28
+ fail-on-severity: high
29
+ pip-audit:
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v6
33
+ with:
34
+ fetch-depth: 0
35
+ - uses: astral-sh/setup-uv@v8.2.0
36
+ with: { enable-cache: true }
37
+ - run: uv sync --frozen
38
+ - run: uvx --quiet pip-audit
@@ -0,0 +1,50 @@
1
+ # Python
2
+ __pycache__/
3
+ src/fabric_dw/_version.py
4
+ *.py[cod]
5
+ *$py.class
6
+ *.so
7
+ .Python
8
+ build/
9
+ dist/
10
+ *.egg-info/
11
+ .eggs/
12
+ .installed.cfg
13
+ *.egg
14
+
15
+ # uv / pip
16
+ .venv/
17
+ venv/
18
+ env/
19
+
20
+ # Test / coverage
21
+ .pytest_cache/
22
+ .coverage
23
+ .coverage.*
24
+ htmlcov/
25
+ coverage.xml
26
+ .tox/
27
+ .nox/
28
+ .cache/
29
+
30
+ # Type checkers
31
+ .mypy_cache/
32
+ .pyright/
33
+ .ruff_cache/
34
+
35
+ # IDE
36
+ .idea/
37
+ .vscode/
38
+ *.swp
39
+
40
+ # OS
41
+ .DS_Store
42
+ Thumbs.db
43
+
44
+ # Env / secrets
45
+ .env
46
+ .env.*
47
+ !.env.example
48
+
49
+ # Docs build output
50
+ docs_build/site/
@@ -0,0 +1 @@
1
+ 3.13