datoon 1.7.0__tar.gz → 1.7.2__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.
- {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/package-installer-smoke.yml +1 -1
- {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/pages.yml +1 -1
- {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/pre-commit.yml +1 -1
- {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/publish-mcp.yml +34 -11
- datoon-1.7.2/.github/workflows/publish.yml +82 -0
- {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/release.yml +1 -1
- {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/tests.yml +2 -2
- {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/validate-artifacts.yml +1 -1
- {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/workflow-lint.yml +2 -2
- {datoon-1.7.0 → datoon-1.7.2}/CHANGELOG.md +14 -0
- {datoon-1.7.0 → datoon-1.7.2}/MARKETPLACES.md +16 -10
- {datoon-1.7.0 → datoon-1.7.2}/PKG-INFO +1 -1
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/_version.py +3 -3
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/mcp_server.py +5 -0
- datoon-1.7.2/src/datoon/readers/_coerce.py +48 -0
- datoon-1.7.2/src/datoon/readers/_tabular.py +46 -0
- datoon-1.7.2/src/datoon/readers/csv.py +17 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/excel.py +3 -9
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/numbers.py +3 -9
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/xml.py +2 -20
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/PKG-INFO +1 -1
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/SOURCES.txt +4 -1
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/scm_file_list.json +3 -0
- datoon-1.7.2/src/datoon.egg-info/scm_version.json +8 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_readers.py +41 -0
- datoon-1.7.2/tests/unit/test_tabular_rows.py +44 -0
- {datoon-1.7.0 → datoon-1.7.2}/uv.lock +6 -6
- datoon-1.7.0/.github/workflows/publish.yml +0 -45
- datoon-1.7.0/src/datoon/readers/csv.py +0 -35
- datoon-1.7.0/src/datoon.egg-info/scm_version.json +0 -8
- {datoon-1.7.0 → datoon-1.7.2}/.agents/plugins/marketplace.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.claude-plugin/marketplace.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.claude-plugin/plugin.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.editorconfig +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.gitattributes +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.github/dependabot.yml +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.gitignore +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.pre-commit-config.yaml +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/.releaserc.yaml +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/CLAUDE.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/CONTRIBUTING.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/INSTALL.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/LICENSE +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/README.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/SECURITY.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/SKILL.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/assets/datoon-banner.svg +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/REPORT.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/agent_results.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/expected_answers.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/generate_payloads.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/1_large.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/1_medium.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/1_small.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/2_large.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/2_medium.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/2_small.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/3_large.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/3_medium.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/3_small.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/1_large.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/1_medium.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/1_small.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/2_large.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/2_medium.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/2_small.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/3_large.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/3_medium.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/3_small.report.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/scored_agent_results.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/1_large.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/1_medium.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/1_small.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/2_large.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/2_medium.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/2_small.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/3_large.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/3_medium.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/3_small.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/payloads.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/results/.gitkeep +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/benchmarks/run.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/datoon/SKILL.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/datoon.skill +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/docs/.nojekyll +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/docs/app.js +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/docs/assets/datoon-banner.svg +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/docs/index.html +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/docs/styles.css +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/glama.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/install.sh +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/plugins/datoon/.codex-plugin/plugin.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/plugins/datoon/skills/datoon/SKILL.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/pyproject.toml +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/scripts/ab_skill_benchmark.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/scripts/install.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/scripts/summarize_agent_skill_eval.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/scripts/validate_plugin_metadata.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/scripts/validate_skill_sync.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/server.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/setup.cfg +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/setup.sh +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/skills/datoon/README.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/skills/datoon/SKILL.md +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/smithery.yaml +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/__init__.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/analyzer.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/cli.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/converter.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/models.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/py.typed +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/__init__.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/columnar.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/jsonl.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/yaml.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/dependency_links.txt +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/entry_points.txt +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/requires.txt +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/top_level.txt +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/fixtures/users.expected.toon +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/fixtures/users.json +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/integration/test_toon_cli_integration.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_analyzer.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_cli.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_converter_policy.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_installer.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_mcp_server.py +0 -0
- {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_models.py +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
name: Publish to MCP Registry
|
|
2
2
|
|
|
3
|
-
# Publishes server.json to the official MCP Registry after a
|
|
3
|
+
# Publishes server.json to the official MCP Registry after a release.
|
|
4
4
|
# Ownership is proven via GitHub OIDC (namespace io.github.andrii-su/*) and the
|
|
5
5
|
# `mcp-name: io.github.andrii-su/datoon` token in the PyPI package README.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# Keys off the Release workflow's completion (GITHUB_TOKEN-created releases do
|
|
8
|
+
# not fire `release: published`). Runs only when main HEAD carries a new version
|
|
9
|
+
# tag, and waits for that version to appear on PyPI before publishing, so it
|
|
10
|
+
# never races the parallel Publish to PyPI workflow.
|
|
9
11
|
|
|
10
12
|
on:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
workflow_run:
|
|
14
|
+
workflows: ["Release"]
|
|
15
|
+
types: [completed]
|
|
13
16
|
workflow_dispatch:
|
|
14
17
|
|
|
15
18
|
permissions:
|
|
@@ -18,16 +21,32 @@ permissions:
|
|
|
18
21
|
|
|
19
22
|
jobs:
|
|
20
23
|
publish:
|
|
24
|
+
if: >-
|
|
25
|
+
github.event_name == 'workflow_dispatch'
|
|
26
|
+
|| github.event.workflow_run.conclusion == 'success'
|
|
21
27
|
runs-on: ubuntu-latest
|
|
22
28
|
steps:
|
|
23
|
-
- uses: actions/checkout@
|
|
29
|
+
- uses: actions/checkout@v7
|
|
30
|
+
with:
|
|
31
|
+
ref: main
|
|
32
|
+
fetch-depth: 0
|
|
24
33
|
|
|
25
|
-
- name: Resolve
|
|
34
|
+
- name: Resolve version to publish
|
|
26
35
|
id: version
|
|
27
|
-
run:
|
|
36
|
+
run: |
|
|
37
|
+
version="$(git tag --points-at HEAD | sed 's/^v//' | head -1)"
|
|
38
|
+
if [ -z "$version" ] && [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
39
|
+
version="$(git describe --tags --abbrev=0 | sed 's/^v//')"
|
|
40
|
+
fi
|
|
41
|
+
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
|
42
|
+
if [ -z "$version" ]; then
|
|
43
|
+
echo "No release tag at HEAD - nothing to publish."
|
|
44
|
+
else
|
|
45
|
+
echo "Publishing datoon ${version} to the MCP Registry."
|
|
46
|
+
fi
|
|
28
47
|
|
|
29
|
-
- name: Sync server.json version
|
|
30
|
-
if:
|
|
48
|
+
- name: Sync server.json version
|
|
49
|
+
if: steps.version.outputs.version != ''
|
|
31
50
|
run: |
|
|
32
51
|
version="${{ steps.version.outputs.version }}"
|
|
33
52
|
tmp="$(mktemp)"
|
|
@@ -38,16 +57,18 @@ jobs:
|
|
|
38
57
|
cat server.json
|
|
39
58
|
|
|
40
59
|
- name: Install mcp-publisher
|
|
60
|
+
if: steps.version.outputs.version != ''
|
|
41
61
|
run: |
|
|
42
62
|
curl -sSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" \
|
|
43
63
|
| tar -xz mcp-publisher
|
|
44
64
|
sudo mv mcp-publisher /usr/local/bin/
|
|
45
65
|
|
|
46
66
|
- name: Validate server.json
|
|
67
|
+
if: steps.version.outputs.version != ''
|
|
47
68
|
run: mcp-publisher validate
|
|
48
69
|
|
|
49
70
|
- name: Wait for the released version to appear on PyPI
|
|
50
|
-
if:
|
|
71
|
+
if: steps.version.outputs.version != ''
|
|
51
72
|
run: |
|
|
52
73
|
version="${{ steps.version.outputs.version }}"
|
|
53
74
|
url="https://pypi.org/pypi/datoon/${version}/json"
|
|
@@ -64,7 +85,9 @@ jobs:
|
|
|
64
85
|
exit 1
|
|
65
86
|
|
|
66
87
|
- name: Login via GitHub OIDC
|
|
88
|
+
if: steps.version.outputs.version != ''
|
|
67
89
|
run: mcp-publisher login github-oidc
|
|
68
90
|
|
|
69
91
|
- name: Publish to MCP Registry
|
|
92
|
+
if: steps.version.outputs.version != ''
|
|
70
93
|
run: mcp-publisher publish
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Releases are cut by the Release workflow using GITHUB_TOKEN, and GitHub does
|
|
4
|
+
# not fire `release: published` for events created by GITHUB_TOKEN. So this
|
|
5
|
+
# workflow keys off the Release workflow's completion instead, and publishes
|
|
6
|
+
# only when the current main HEAD carries a new version tag.
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
workflow_run:
|
|
10
|
+
workflows: ["Release"]
|
|
11
|
+
types: [completed]
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build:
|
|
20
|
+
if: >-
|
|
21
|
+
github.event_name == 'workflow_dispatch'
|
|
22
|
+
|| github.event.workflow_run.conclusion == 'success'
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
outputs:
|
|
25
|
+
version: ${{ steps.ver.outputs.version }}
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v7
|
|
28
|
+
with:
|
|
29
|
+
ref: main
|
|
30
|
+
fetch-depth: 0
|
|
31
|
+
|
|
32
|
+
- name: Resolve version to publish
|
|
33
|
+
id: ver
|
|
34
|
+
run: |
|
|
35
|
+
version="$(git tag --points-at HEAD | sed 's/^v//' | head -1)"
|
|
36
|
+
if [ -z "$version" ] && [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
37
|
+
version="$(git describe --tags --abbrev=0 | sed 's/^v//')"
|
|
38
|
+
fi
|
|
39
|
+
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
|
40
|
+
if [ -z "$version" ]; then
|
|
41
|
+
echo "No release tag at HEAD - nothing to publish."
|
|
42
|
+
else
|
|
43
|
+
echo "Publishing datoon ${version}."
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
- name: Check out the exact release tag
|
|
47
|
+
if: steps.ver.outputs.version != ''
|
|
48
|
+
run: git checkout "v${{ steps.ver.outputs.version }}"
|
|
49
|
+
|
|
50
|
+
- name: Install uv
|
|
51
|
+
if: steps.ver.outputs.version != ''
|
|
52
|
+
uses: astral-sh/setup-uv@v7
|
|
53
|
+
|
|
54
|
+
- name: Build distributions
|
|
55
|
+
if: steps.ver.outputs.version != ''
|
|
56
|
+
run: uv build
|
|
57
|
+
|
|
58
|
+
- name: Upload distributions
|
|
59
|
+
if: steps.ver.outputs.version != ''
|
|
60
|
+
uses: actions/upload-artifact@v7
|
|
61
|
+
with:
|
|
62
|
+
name: dist
|
|
63
|
+
path: dist/
|
|
64
|
+
|
|
65
|
+
publish:
|
|
66
|
+
needs: build
|
|
67
|
+
if: needs.build.outputs.version != ''
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
environment:
|
|
70
|
+
name: pypi
|
|
71
|
+
url: https://pypi.org/project/datoon/
|
|
72
|
+
steps:
|
|
73
|
+
- name: Download distributions
|
|
74
|
+
uses: actions/download-artifact@v8
|
|
75
|
+
with:
|
|
76
|
+
name: dist
|
|
77
|
+
path: dist/
|
|
78
|
+
|
|
79
|
+
- name: Publish to PyPI
|
|
80
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
81
|
+
with:
|
|
82
|
+
skip-existing: true
|
|
@@ -15,7 +15,7 @@ jobs:
|
|
|
15
15
|
runs-on: ubuntu-latest
|
|
16
16
|
steps:
|
|
17
17
|
- name: Checkout
|
|
18
|
-
uses: actions/checkout@
|
|
18
|
+
uses: actions/checkout@v7
|
|
19
19
|
|
|
20
20
|
- name: Setup uv
|
|
21
21
|
uses: astral-sh/setup-uv@v7
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
runs-on: ubuntu-latest
|
|
28
28
|
steps:
|
|
29
29
|
- name: Checkout
|
|
30
|
-
uses: actions/checkout@
|
|
30
|
+
uses: actions/checkout@v7
|
|
31
31
|
|
|
32
32
|
- name: Setup uv
|
|
33
33
|
uses: astral-sh/setup-uv@v7
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
runs-on: ubuntu-latest
|
|
23
23
|
steps:
|
|
24
24
|
- name: Checkout
|
|
25
|
-
uses: actions/checkout@
|
|
25
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
|
|
26
26
|
with:
|
|
27
27
|
persist-credentials: false
|
|
28
28
|
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
runs-on: ubuntu-latest
|
|
36
36
|
steps:
|
|
37
37
|
- name: Checkout
|
|
38
|
-
uses: actions/checkout@
|
|
38
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v6
|
|
39
39
|
with:
|
|
40
40
|
persist-credentials: false
|
|
41
41
|
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.7.2](https://github.com/andrii-su/datoon/compare/v1.7.1...v1.7.2) (2026-07-07)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **readers:** preserve semantic values during scalar and row coercion ([#36](https://github.com/andrii-su/datoon/issues/36)) ([5b24c00](https://github.com/andrii-su/datoon/commit/5b24c0098da620e14158b457a04d639663ba33f0))
|
|
7
|
+
|
|
8
|
+
## [1.7.1](https://github.com/andrii-su/datoon/compare/v1.7.0...v1.7.1) (2026-06-22)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **mcp:** report datoon version in the MCP initialize handshake ([22ba5fd](https://github.com/andrii-su/datoon/commit/22ba5fd23071cf2fa6a2d2c51d306d761dce9dc4))
|
|
14
|
+
|
|
1
15
|
# [1.7.0](https://github.com/andrii-su/datoon/compare/v1.6.0...v1.7.0) (2026-06-22)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -22,17 +22,25 @@ ______________________________________________________________________
|
|
|
22
22
|
[`README.md`](./README.md) (PyPI renders the README as the package description,
|
|
23
23
|
which the registry validates).
|
|
24
24
|
- **Publishing:** [`.github/workflows/publish-mcp.yml`](./.github/workflows/publish-mcp.yml)
|
|
25
|
-
runs
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
runs when the **Release** workflow completes (via `workflow_run`), and only
|
|
26
|
+
when main HEAD carries a new version tag. It syncs `server.json`'s version to
|
|
27
|
+
that tag, validates, waits for the version to appear on PyPI, authenticates via
|
|
28
|
+
**GitHub OIDC** (no stored secret), and runs `mcp-publisher publish`.
|
|
28
29
|
|
|
29
30
|
> [!NOTE]
|
|
30
|
-
>
|
|
31
|
-
>
|
|
32
|
-
>
|
|
33
|
-
>
|
|
31
|
+
> **Trigger note.** Releases are cut by semantic-release using `GITHUB_TOKEN`,
|
|
32
|
+
> and GitHub does not fire `release: published` for `GITHUB_TOKEN`-created
|
|
33
|
+
> events. Both [`publish.yml`](./.github/workflows/publish.yml) (PyPI) and
|
|
34
|
+
> publish-mcp.yml therefore key off the **Release** workflow's completion
|
|
35
|
+
> instead, gated on a new tag at HEAD.
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
> [!NOTE]
|
|
38
|
+
> The ownership token first reaches PyPI with the release that includes the
|
|
39
|
+
> marketplace change. The first successful publish happens on the next release
|
|
40
|
+
> after that merge.
|
|
41
|
+
|
|
42
|
+
Manual publish — **Publish to MCP Registry** workflow → **Run workflow**
|
|
43
|
+
(`workflow_dispatch`); it publishes the latest tag. Or locally:
|
|
36
44
|
|
|
37
45
|
```bash
|
|
38
46
|
# Install mcp-publisher (see modelcontextprotocol/registry releases)
|
|
@@ -41,8 +49,6 @@ mcp-publisher login github # interactive OAuth, namespace io.github.andri
|
|
|
41
49
|
mcp-publisher publish
|
|
42
50
|
```
|
|
43
51
|
|
|
44
|
-
Re-run via the **Publish to MCP Registry** workflow's `workflow_dispatch`.
|
|
45
|
-
|
|
46
52
|
______________________________________________________________________
|
|
47
53
|
|
|
48
54
|
## 2. Smithery — one-time connect
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 7,
|
|
21
|
+
__version__ = version = '1.7.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 7, 2)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'ga011eb92d'
|
|
@@ -12,6 +12,7 @@ except ImportError as exc:
|
|
|
12
12
|
"MCP support requires the 'mcp' extra: pip install datoon[mcp]"
|
|
13
13
|
) from exc
|
|
14
14
|
|
|
15
|
+
from datoon import __version__
|
|
15
16
|
from datoon.analyzer import analyze_payload
|
|
16
17
|
from datoon.converter import DatoonError, convert_json_for_llm
|
|
17
18
|
from datoon.models import ConversionConfig
|
|
@@ -25,6 +26,10 @@ mcp = FastMCP(
|
|
|
25
26
|
"Use analyze_json to check if a payload is a good TOON candidate without converting."
|
|
26
27
|
),
|
|
27
28
|
)
|
|
29
|
+
# FastMCP has no `version` parameter; set it on the underlying server so the
|
|
30
|
+
# MCP `initialize` handshake reports datoon's version instead of the mcp
|
|
31
|
+
# library's default.
|
|
32
|
+
mcp._mcp_server.version = __version__
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
@mcp.tool()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Shared scalar coercion for text readers (CSV, XML).
|
|
2
|
+
|
|
3
|
+
Coercion must never change semantic values. Two traps are guarded here:
|
|
4
|
+
|
|
5
|
+
* Leading-zero integers (ZIP codes, phone numbers, IDs) stay strings — only
|
|
6
|
+
values that round-trip through ``int`` are coerced.
|
|
7
|
+
* ``inf``/``nan`` and overflowing exponents would serialize to ``Infinity``/
|
|
8
|
+
``NaN`` (invalid JSON), so non-finite floats stay strings.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import math
|
|
14
|
+
import re
|
|
15
|
+
|
|
16
|
+
# Canonical decimal/scientific literal: no leading zeros, no underscores. Guards
|
|
17
|
+
# against float("00755") -> 755.0 and float("1_000") -> 1000.0 quietly rewriting
|
|
18
|
+
# IDs and formatted numbers.
|
|
19
|
+
_FLOAT_RE = re.compile(r"^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def coerce_scalar(value: str) -> int | float | bool | str | None:
|
|
23
|
+
"""Coerce a raw string cell to a JSON-safe scalar without losing meaning."""
|
|
24
|
+
stripped = value.strip()
|
|
25
|
+
if stripped == "":
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
low = stripped.lower()
|
|
29
|
+
if low == "true":
|
|
30
|
+
return True
|
|
31
|
+
if low == "false":
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
# Only accept integers that round-trip: rejects "00123", "+5", "1_000".
|
|
35
|
+
try:
|
|
36
|
+
as_int = int(stripped)
|
|
37
|
+
except ValueError:
|
|
38
|
+
pass
|
|
39
|
+
else:
|
|
40
|
+
if str(as_int) == stripped:
|
|
41
|
+
return as_int
|
|
42
|
+
|
|
43
|
+
if _FLOAT_RE.match(stripped):
|
|
44
|
+
as_float = float(stripped)
|
|
45
|
+
# Reject overflowing exponents (1e400 -> inf) that break JSON serialization.
|
|
46
|
+
if math.isfinite(as_float):
|
|
47
|
+
return as_float
|
|
48
|
+
return stripped
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Shared header/data-row normalization for spreadsheet readers (Excel, Numbers)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def header_rows_to_dicts(rows: list[Any]) -> list[dict[str, Any]]:
|
|
9
|
+
"""Turn ``[header, *data]`` cell tuples into row dicts with stable keys.
|
|
10
|
+
|
|
11
|
+
* A ``None`` header cell becomes ``col{index}``.
|
|
12
|
+
* Data rows wider than the header keep their overflow cells under synthetic
|
|
13
|
+
``col{index}`` keys instead of being silently dropped.
|
|
14
|
+
* Data rows narrower than the header get ``None`` for missing trailing cells,
|
|
15
|
+
so every emitted row shares an identical key set.
|
|
16
|
+
* Rows whose cells are all ``None`` are skipped.
|
|
17
|
+
"""
|
|
18
|
+
if not rows:
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
data_rows = rows[1:]
|
|
22
|
+
if not data_rows:
|
|
23
|
+
return []
|
|
24
|
+
|
|
25
|
+
widest = max((len(row) for row in data_rows), default=0)
|
|
26
|
+
header_row = rows[0]
|
|
27
|
+
column_count = max(len(header_row), widest)
|
|
28
|
+
|
|
29
|
+
headers: list[str] = []
|
|
30
|
+
for i in range(column_count):
|
|
31
|
+
raw = header_row[i] if i < len(header_row) else None
|
|
32
|
+
headers.append(str(raw) if raw is not None else f"col{i}")
|
|
33
|
+
|
|
34
|
+
result: list[dict[str, Any]] = []
|
|
35
|
+
for row in data_rows:
|
|
36
|
+
if not any(cell is not None for cell in row):
|
|
37
|
+
continue
|
|
38
|
+
result.append(dict(zip(headers, _pad(row, column_count))))
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _pad(row: Any, width: int) -> list[Any]:
|
|
43
|
+
cells = list(row)
|
|
44
|
+
if len(cells) < width:
|
|
45
|
+
cells.extend([None] * (width - len(cells)))
|
|
46
|
+
return cells
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""CSV reader with basic type coercion."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import csv
|
|
6
|
+
import io
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from datoon.readers._coerce import coerce_scalar
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def read_csv(text: str, *, coerce_types: bool = True) -> list[dict[str, Any]]:
|
|
13
|
+
"""Parse CSV text into a list of row dicts."""
|
|
14
|
+
reader = csv.DictReader(io.StringIO(text))
|
|
15
|
+
if coerce_types:
|
|
16
|
+
return [{k: coerce_scalar(str(v)) for k, v in row.items()} for row in reader]
|
|
17
|
+
return [dict(row) for row in reader]
|
|
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from datoon.readers._tabular import header_rows_to_dicts
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
def read_excel(path: Path, *, sheet: int = 0) -> list[dict[str, Any]]:
|
|
10
12
|
"""Read the first sheet of an Excel file into a list of row dicts."""
|
|
@@ -20,12 +22,4 @@ def read_excel(path: Path, *, sheet: int = 0) -> list[dict[str, Any]]:
|
|
|
20
22
|
rows = list(ws.iter_rows(values_only=True))
|
|
21
23
|
wb.close()
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
return []
|
|
25
|
-
|
|
26
|
-
headers = [str(h) if h is not None else f"col{i}" for i, h in enumerate(rows[0])]
|
|
27
|
-
return [
|
|
28
|
-
{h: v for h, v in zip(headers, row)}
|
|
29
|
-
for row in rows[1:]
|
|
30
|
-
if any(v is not None for v in row)
|
|
31
|
-
]
|
|
25
|
+
return header_rows_to_dicts(rows)
|
|
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from datoon.readers._tabular import header_rows_to_dicts
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
def read_numbers(path: Path, *, sheet: int = 0, table: int = 0) -> list[dict[str, Any]]:
|
|
10
12
|
"""Read an Apple Numbers file into a list of row dicts."""
|
|
@@ -19,12 +21,4 @@ def read_numbers(path: Path, *, sheet: int = 0, table: int = 0) -> list[dict[str
|
|
|
19
21
|
tbl = doc.sheets[sheet].tables[table]
|
|
20
22
|
rows = list(tbl.rows(values_only=True))
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
return []
|
|
24
|
-
|
|
25
|
-
headers = [str(h) if h is not None else f"col{i}" for i, h in enumerate(rows[0])]
|
|
26
|
-
return [
|
|
27
|
-
{h: v for h, v in zip(headers, row)}
|
|
28
|
-
for row in rows[1:]
|
|
29
|
-
if any(v is not None for v in row)
|
|
30
|
-
]
|
|
24
|
+
return header_rows_to_dicts(rows)
|
|
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
import xml.etree.ElementTree as ET
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from datoon.readers._coerce import coerce_scalar as _coerce
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
def read_xml(text: str) -> list[dict[str, Any]]:
|
|
10
12
|
"""Parse XML into a list of row dicts from the dominant repeated child element."""
|
|
@@ -36,23 +38,3 @@ def _element_to_dict(element: ET.Element) -> dict[str, Any]:
|
|
|
36
38
|
if not list(element) and element.text and element.text.strip():
|
|
37
39
|
result["_text"] = element.text.strip()
|
|
38
40
|
return result
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _coerce(value: str) -> int | float | bool | str | None:
|
|
42
|
-
stripped = value.strip()
|
|
43
|
-
if not stripped:
|
|
44
|
-
return None
|
|
45
|
-
low = stripped.lower()
|
|
46
|
-
if low == "true":
|
|
47
|
-
return True
|
|
48
|
-
if low == "false":
|
|
49
|
-
return False
|
|
50
|
-
try:
|
|
51
|
-
return int(stripped)
|
|
52
|
-
except ValueError:
|
|
53
|
-
pass
|
|
54
|
-
try:
|
|
55
|
-
return float(stripped)
|
|
56
|
-
except ValueError:
|
|
57
|
-
pass
|
|
58
|
-
return stripped
|
|
@@ -103,6 +103,8 @@ src/datoon.egg-info/scm_file_list.json
|
|
|
103
103
|
src/datoon.egg-info/scm_version.json
|
|
104
104
|
src/datoon.egg-info/top_level.txt
|
|
105
105
|
src/datoon/readers/__init__.py
|
|
106
|
+
src/datoon/readers/_coerce.py
|
|
107
|
+
src/datoon/readers/_tabular.py
|
|
106
108
|
src/datoon/readers/columnar.py
|
|
107
109
|
src/datoon/readers/csv.py
|
|
108
110
|
src/datoon/readers/excel.py
|
|
@@ -119,4 +121,5 @@ tests/unit/test_converter_policy.py
|
|
|
119
121
|
tests/unit/test_installer.py
|
|
120
122
|
tests/unit/test_mcp_server.py
|
|
121
123
|
tests/unit/test_models.py
|
|
122
|
-
tests/unit/test_readers.py
|
|
124
|
+
tests/unit/test_readers.py
|
|
125
|
+
tests/unit/test_tabular_rows.py
|
|
@@ -40,11 +40,13 @@
|
|
|
40
40
|
"src/datoon/converter.py",
|
|
41
41
|
"src/datoon/cli.py",
|
|
42
42
|
"src/datoon/readers/xml.py",
|
|
43
|
+
"src/datoon/readers/_coerce.py",
|
|
43
44
|
"src/datoon/readers/__init__.py",
|
|
44
45
|
"src/datoon/readers/jsonl.py",
|
|
45
46
|
"src/datoon/readers/numbers.py",
|
|
46
47
|
"src/datoon/readers/columnar.py",
|
|
47
48
|
"src/datoon/readers/csv.py",
|
|
49
|
+
"src/datoon/readers/_tabular.py",
|
|
48
50
|
"src/datoon/readers/excel.py",
|
|
49
51
|
"src/datoon/readers/yaml.py",
|
|
50
52
|
"assets/datoon-banner.svg",
|
|
@@ -59,6 +61,7 @@
|
|
|
59
61
|
"tests/unit/test_models.py",
|
|
60
62
|
"tests/unit/test_converter_policy.py",
|
|
61
63
|
"tests/unit/test_mcp_server.py",
|
|
64
|
+
"tests/unit/test_tabular_rows.py",
|
|
62
65
|
"tests/unit/test_analyzer.py",
|
|
63
66
|
"tests/unit/test_installer.py",
|
|
64
67
|
"tests/unit/test_cli.py",
|