mindoff-dataport 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.
@@ -0,0 +1,141 @@
1
+ name: CD
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ concurrency:
8
+ group: cd-${{ github.ref_name }}
9
+ cancel-in-progress: false
10
+
11
+ jobs:
12
+ validate-tag:
13
+ name: Validate release tag
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Check tag format
17
+ run: |
18
+ TAG="${{ github.ref_name }}"
19
+ echo "[INFO] Release tag: $TAG"
20
+ if ! echo "$TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
21
+ echo "[ERROR] Tag '$TAG' does not match required format vX.Y.Z — aborting."
22
+ exit 1
23
+ fi
24
+ echo "[INFO] Tag format is valid."
25
+
26
+ version-changelog:
27
+ name: Version changelog
28
+ runs-on: ubuntu-latest
29
+ needs: validate-tag
30
+ permissions:
31
+ contents: write
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ with:
35
+ ref: root
36
+ token: ${{ secrets.PAT_TOKEN }}
37
+
38
+ - name: Replace "Recent Changes" with release version
39
+ run: |
40
+ VERSION="${{ github.ref_name }}"
41
+ sed -i "s/^## Recent Changes$/## ${VERSION}/" CHANGELOG.md
42
+ echo "[INFO] Replaced 'Recent Changes' with '${VERSION}'"
43
+
44
+ - name: Commit versioned changelog
45
+ run: |
46
+ git config user.name "github-actions[bot]"
47
+ git config user.email "github-actions[bot]@users.noreply.github.com"
48
+ git add CHANGELOG.md
49
+ git commit -m "📝 Update changelog for release: ${{ github.ref_name }}"
50
+ git push
51
+
52
+ publish:
53
+ name: Publish to PyPI
54
+ runs-on: ubuntu-latest
55
+ needs: version-changelog
56
+ permissions:
57
+ id-token: write
58
+ contents: write
59
+ steps:
60
+ - uses: actions/checkout@v4
61
+ with:
62
+ ref: root
63
+ token: ${{ secrets.PAT_TOKEN }}
64
+
65
+ - uses: actions/setup-python@v5
66
+ with:
67
+ python-version: "3.13"
68
+ cache: pip
69
+
70
+ - name: Install build tools
71
+ run: |
72
+ python -m pip install --upgrade pip
73
+ pip install build twine
74
+
75
+ - name: Sync pyproject.toml version with release tag
76
+ run: |
77
+ TAG_VERSION="${{ github.ref_name }}"
78
+ TAG_VERSION="${TAG_VERSION#v}"
79
+ PROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
80
+ echo "[INFO] Tag: $TAG_VERSION | pyproject.toml: $PROJECT_VERSION"
81
+
82
+ if [ "$TAG_VERSION" != "$PROJECT_VERSION" ]; then
83
+ echo "[WARN] Version mismatch — updating pyproject.toml to $TAG_VERSION"
84
+ sed -i "s/^version = \".*\"/version = \"${TAG_VERSION}\"/" pyproject.toml
85
+
86
+ UPDATED=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
87
+ if [ "$UPDATED" != "$TAG_VERSION" ]; then
88
+ echo "[ERROR] Failed to update version in pyproject.toml — aborting."
89
+ exit 1
90
+ fi
91
+
92
+ git config user.name "github-actions[bot]"
93
+ git config user.email "github-actions[bot]@users.noreply.github.com"
94
+ git add pyproject.toml
95
+ git commit -m "🔖 Bump version to ${TAG_VERSION} to match release tag"
96
+ git push
97
+ echo "[INFO] pyproject.toml updated and pushed."
98
+ else
99
+ echo "[INFO] Versions match — no update needed."
100
+ fi
101
+
102
+ - name: Remove old build artifacts
103
+ run: rm -rf dist/ build/ *.egg-info
104
+
105
+ - name: Build package
106
+ run: python -m build
107
+
108
+ - name: Validate distribution metadata
109
+ run: python -m twine check dist/*
110
+
111
+ - name: Publish to PyPI
112
+ uses: pypa/gh-action-pypi-publish@release/v1
113
+ with:
114
+ packages-dir: dist/
115
+
116
+ revert-changelog:
117
+ name: Revert changelog on failure
118
+ runs-on: ubuntu-latest
119
+ needs: publish
120
+ if: failure()
121
+ permissions:
122
+ contents: write
123
+ steps:
124
+ - uses: actions/checkout@v4
125
+ with:
126
+ ref: root
127
+ token: ${{ secrets.PAT_TOKEN }}
128
+
129
+ - name: Revert versioned heading back to "Recent Changes"
130
+ run: |
131
+ VERSION="${{ github.ref_name }}"
132
+ sed -i "s/^## ${VERSION}$/## Recent Changes/" CHANGELOG.md
133
+ echo "[INFO] Reverted '${VERSION}' back to 'Recent Changes'"
134
+
135
+ - name: Commit revert
136
+ run: |
137
+ git config user.name "github-actions[bot]"
138
+ git config user.email "github-actions[bot]@users.noreply.github.com"
139
+ git add CHANGELOG.md
140
+ git commit -m "⏪ Revert changelog — release ${{ github.ref_name }} failed"
141
+ git push
@@ -0,0 +1,108 @@
1
+ name: PR CI
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened, edited]
6
+ branches:
7
+ - root
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: ci-${{ github.event.pull_request.number || github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ smoke-minimal:
16
+ name: Smoke (minimal install)
17
+ runs-on: ubuntu-latest
18
+ if: github.event.action != 'labeled' && github.event.action != 'unlabeled' && github.event.action != 'edited'
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.13"
25
+ cache: pip
26
+
27
+ - name: Install minimal package
28
+ run: |
29
+ python -m pip install --upgrade pip
30
+ pip install -e .
31
+
32
+ - name: Import smoke check
33
+ run: python -c "import mindoff_dataport; print('smoke ok')"
34
+
35
+ test:
36
+ name: Test (Python ${{ matrix.python-version }})
37
+ runs-on: ubuntu-latest
38
+ if: github.event.action != 'labeled' && github.event.action != 'unlabeled' && github.event.action != 'edited'
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ python-version: ["3.12", "3.13"]
43
+ env:
44
+ PYTHONDONTWRITEBYTECODE: "1"
45
+ PYTHONUNBUFFERED: "1"
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+
49
+ - uses: actions/setup-python@v5
50
+ with:
51
+ python-version: ${{ matrix.python-version }}
52
+ cache: pip
53
+
54
+ - name: Install dependencies
55
+ run: |
56
+ python -m pip install --upgrade pip
57
+ pip install -e ".[dev]"
58
+
59
+ - name: Run tests with coverage (min 90%)
60
+ run: |
61
+ PYTHONPATH=src python -m pytest \
62
+ --cov=mindoff_dataport \
63
+ --cov-branch \
64
+ --cov-report=xml \
65
+ --cov-fail-under=90
66
+
67
+ - name: Upload coverage report
68
+ uses: actions/upload-artifact@v4
69
+ with:
70
+ name: coverage-py${{ matrix.python-version }}
71
+ path: coverage.xml
72
+
73
+ lint-pr-title:
74
+ name: Lint PR title
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - name: Check PR title and label
78
+ uses: actions/github-script@v7
79
+ with:
80
+ script: |
81
+ const title = context.payload.pull_request.title;
82
+ const labels = context.payload.pull_request.labels.map(l => l.name);
83
+
84
+ const titleRegex = /^\p{Emoji}[\p{Emoji}️⃣‍]*\s+[A-Z][a-z]+/u;
85
+ if (!titleRegex.test(title)) {
86
+ core.setFailed(
87
+ `PR title "${title}" does not match the required format.\n` +
88
+ `Expected: {emoji} {Sentence starting with a capitalised Verb}\n` +
89
+ `Example: ✨ Add dataframe repeat support`
90
+ );
91
+ }
92
+
93
+ const allowedLabels = [
94
+ "bug",
95
+ "feature",
96
+ "enhancement",
97
+ "documentation",
98
+ "internal",
99
+ ];
100
+
101
+ const hasAllowed = labels.some(l => allowedLabels.includes(l));
102
+ if (!hasAllowed) {
103
+ core.setFailed(
104
+ `PR must have at least one of the allowed labels:\n` +
105
+ allowedLabels.join(", ") + "\n" +
106
+ `Current labels: ${labels.join(", ") || "(none)"}`
107
+ );
108
+ }
@@ -0,0 +1,99 @@
1
+ name: PR Merge CI
2
+
3
+ on:
4
+ pull_request:
5
+ types: [closed]
6
+ branches:
7
+ - root
8
+
9
+ concurrency:
10
+ group: merge-${{ github.ref }}
11
+ cancel-in-progress: false
12
+
13
+ jobs:
14
+ update-changelog:
15
+ name: Update changelog
16
+ runs-on: ubuntu-latest
17
+ if: |
18
+ github.event.pull_request.merged == true &&
19
+ contains(fromJson('["root"]'), github.base_ref)
20
+ permissions:
21
+ contents: write
22
+ pull-requests: read
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ with:
26
+ ref: ${{ github.base_ref }}
27
+ token: ${{ secrets.PAT_TOKEN }}
28
+
29
+ - name: Append entry to CHANGELOG.md
30
+ uses: actions/github-script@v7
31
+ with:
32
+ script: |
33
+ const fs = require("fs");
34
+
35
+ const pr = context.payload.pull_request;
36
+ const title = pr.title;
37
+ const number = pr.number;
38
+ const prUrl = pr.html_url;
39
+ const labels = pr.labels.map(l => l.name);
40
+
41
+ const labelToCategory = {
42
+ "bug": "Fixes",
43
+ "feature": "Features",
44
+ "enhancement": "Enhancements",
45
+ "documentation": "Documentation",
46
+ "internal": "Internal",
47
+ };
48
+
49
+ const allowedLabels = Object.keys(labelToCategory);
50
+ const matchedLabel = labels.find(l => allowedLabels.includes(l));
51
+ const category = matchedLabel ? labelToCategory[matchedLabel] : "Miscellaneous";
52
+
53
+ const entry = `- ${title} ([#${number}](${prUrl}))`;
54
+
55
+ let changelog = fs.readFileSync("CHANGELOG.md", "utf8");
56
+
57
+ const recentHeader = "## Recent Changes";
58
+ const categoryHeader = `### ${category}`;
59
+
60
+ if (!changelog.includes(recentHeader)) {
61
+ const titleLine = "<h1>Release Notes</h1>";
62
+ if (changelog.includes(titleLine)) {
63
+ const titleIdx = changelog.indexOf(titleLine);
64
+ const insertPos = changelog.indexOf("\n", titleIdx + titleLine.length);
65
+ changelog =
66
+ changelog.slice(0, insertPos + 1) +
67
+ `\n${recentHeader}\n\n` +
68
+ changelog.slice(insertPos + 1);
69
+ } else {
70
+ changelog = `${recentHeader}\n\n${changelog}`;
71
+ }
72
+ }
73
+
74
+ const recentIdx = changelog.indexOf(recentHeader);
75
+ const afterRecent = changelog.indexOf("\n## ", recentIdx + recentHeader.length);
76
+ const blockEnd = afterRecent === -1 ? changelog.length : afterRecent;
77
+
78
+ let block = changelog.slice(recentIdx, blockEnd);
79
+
80
+ if (block.includes(categoryHeader)) {
81
+ block = block.replace(
82
+ new RegExp(`(${categoryHeader}[^\\n]*)`),
83
+ `$1\n${entry}`
84
+ );
85
+ } else {
86
+ block = block.trimEnd() + `\n\n${categoryHeader}\n${entry}\n`;
87
+ }
88
+
89
+ changelog = changelog.slice(0, recentIdx) + block + changelog.slice(blockEnd);
90
+ fs.writeFileSync("CHANGELOG.md", changelog, "utf8");
91
+ console.log("CHANGELOG.md updated successfully.");
92
+
93
+ - name: Commit changelog update
94
+ run: |
95
+ git config user.name "github-actions[bot]"
96
+ git config user.email "github-actions[bot]@users.noreply.github.com"
97
+ git add CHANGELOG.md
98
+ git diff --cached --quiet || git commit -m "📝 Update changelog for PR #${{ github.event.pull_request.number }}"
99
+ git push
@@ -0,0 +1,81 @@
1
+ name: Root CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - root
7
+ paths:
8
+ - "src/**"
9
+ - "pyproject.toml"
10
+ - "pytest.ini"
11
+ - ".github/workflows/root_ci.yml"
12
+ workflow_dispatch:
13
+
14
+ concurrency:
15
+ group: root-stability-${{ github.sha }}
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ smoke-minimal:
20
+ name: Smoke (minimal install)
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.13"
28
+ cache: pip
29
+
30
+ - name: Install minimal package
31
+ run: |
32
+ python -m pip install --upgrade pip
33
+ pip install -e .
34
+
35
+ - name: Import smoke check
36
+ run: python -c "import mindoff_dataport; print('smoke ok')"
37
+
38
+ test:
39
+ name: Test (Python ${{ matrix.python-version }})
40
+ runs-on: ubuntu-latest
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
45
+ env:
46
+ PYTHONDONTWRITEBYTECODE: "1"
47
+ PYTHONUNBUFFERED: "1"
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - uses: actions/setup-python@v5
52
+ with:
53
+ python-version: ${{ matrix.python-version }}
54
+ cache: pip
55
+
56
+ - name: Install dependencies
57
+ run: |
58
+ python -m pip install --upgrade pip
59
+ pip install -e ".[dev]"
60
+
61
+ - name: Run tests with coverage (min 90%)
62
+ run: |
63
+ PYTHONPATH=src python -m pytest \
64
+ --cov=mindoff_dataport \
65
+ --cov-branch \
66
+ --cov-report=xml \
67
+ --cov-fail-under=90
68
+
69
+ - name: Upload coverage to Codecov
70
+ uses: codecov/codecov-action@v4
71
+ with:
72
+ files: coverage.xml
73
+ flags: root
74
+ name: root-ci
75
+ fail_ci_if_error: true
76
+
77
+ - name: Upload coverage report
78
+ uses: actions/upload-artifact@v4
79
+ with:
80
+ name: coverage-py${{ matrix.python-version }}
81
+ path: coverage.xml
@@ -0,0 +1,106 @@
1
+ # Python bytecode and caches
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Virtual environments
7
+ .venv/
8
+ venv/
9
+ env/
10
+ ENV/
11
+
12
+ # Test and coverage artifacts
13
+ .pytest_cache/
14
+ .pytest_tmp/
15
+ .pytest_tmp*/
16
+ .tmp/
17
+ .coverage
18
+ .coverage.*
19
+ htmlcov/
20
+ temp_pytest_workspace/
21
+
22
+ # Packaging / build artifacts
23
+ build/
24
+ dist/
25
+ *.egg-info/
26
+ .eggs/
27
+ *.whl
28
+ pip-wheel-metadata/
29
+
30
+ # Type checkers / linters
31
+ .mypy_cache/
32
+ .pyre/
33
+ .ruff_cache/
34
+
35
+ # Jupyter
36
+ .ipynb_checkpoints/
37
+
38
+ # IDE/editor files
39
+ .vscode/
40
+ .idea/
41
+ *.swp
42
+ *.swo
43
+
44
+ # OS files
45
+ .DS_Store
46
+ Thumbs.db
47
+
48
+ # Local tooling
49
+ .claude/
50
+ CLAUDE.md
51
+ claude.md
52
+ AGENTS.md
53
+
54
+ # Generated example outputs
55
+ examples/output/*
56
+ !examples/output/.gitkeep
57
+ examples/repeat_block/output.xlsx
58
+ examples/repeat_block/output.pdf
59
+ examples/repeat_block/output.part*.xlsx
60
+ examples/repeat_block/report_bundle_*/
61
+ examples/basic/output.xlsx
62
+ examples/basic/output.pdf
63
+ examples/basic/output.part*.xlsx
64
+ examples/basic/report_bundle_*/
65
+ examples/repeat_block_multiple/output.xlsx
66
+ examples/repeat_block_multiple/output.pdf
67
+ examples/repeat_block_multiple/output.part*.xlsx
68
+ examples/repeat_block_multiple/report_bundle_*/
69
+ examples/dynamic_sheets/output.xlsx
70
+ examples/dynamic_sheets/output.pdf
71
+ examples/dynamic_sheets/output.part*.xlsx
72
+ examples/dynamic_sheets/report_bundle_*/
73
+ examples/dataframe_header_content/output*.xlsx
74
+ examples/dataframe_header_content/output.pdf
75
+ examples/dataframe_header_content/report_bundle_*/
76
+ examples/streaming_split_workbooks/output*.xlsx
77
+ examples/streaming_split_workbooks/output*.zip
78
+ examples/streaming_split_workbooks/report_bundle_*/
79
+ examples/sizing_modes/output*.xlsx
80
+ examples/sizing_modes/report_bundle_*/
81
+ examples/merged_cells/output*.xlsx
82
+ examples/merged_cells/output.pdf
83
+ examples/merged_cells/report_bundle_*/
84
+ examples/formulas/output*.xlsx
85
+ examples/formulas/report_bundle_*/
86
+ examples/gridlines_and_styles/output*.xlsx
87
+ examples/gridlines_and_styles/output.pdf
88
+ examples/gridlines_and_styles/report_bundle_*/
89
+ examples/pdf_pagination/output.pdf
90
+ examples/pdf_pagination/report_bundle_*/
91
+ examples/pdf_custom_fonts/output.pdf
92
+ examples/pdf_custom_fonts/report_bundle_*/
93
+ examples/bundle_path/output*.xlsx
94
+ examples/bundle_path/report_bundle/
95
+ examples/auto_delete_bundle/output*.xlsx
96
+ examples/auto_delete_bundle/temporary_bundle/
97
+ examples/lazy_parquet_large/output*.xlsx
98
+ examples/lazy_parquet_large/report_bundle_*/
99
+ examples/multi_sheet_static/output*.xlsx
100
+ examples/multi_sheet_static/report_bundle_*/
101
+ examples/image_reserved/output.*
102
+ examples/image_reserved/report_bundle_*/
103
+
104
+ # Transient streaming export scratch dirs (created during local/debug runs)
105
+ streaming_*/
106
+ concept/
@@ -0,0 +1,22 @@
1
+ <h1>Release Notes</h1>
2
+
3
+ ## v0.1.0
4
+
5
+ ### Enhancements
6
+ - ✨ Add parquet-backed PDF and XLSX export support
7
+ - ✨ Add hybrid parquet streaming bundle
8
+ - ✨ Add streaming export support
9
+ - ✨ Update renderer and streaming
10
+ - ✨ Update export API and guidance
11
+
12
+ ### Internal
13
+ - ♻️ Refactor export pipeline
14
+ - ♻️ Rename package to mindoff_dataport
15
+ - ♻️ Move tests into src/tests
16
+ - ♻️ Trim examples to core features only
17
+ - ♻️ Promote mo_dataport API alias and expand package metadata
18
+ - 🔧 Refresh project setup
19
+ - 🙈 Move example outputs under ignored folder
20
+
21
+ ### Documentation
22
+ - 📝 Refresh README and example import alias