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.
Files changed (130) hide show
  1. {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/package-installer-smoke.yml +1 -1
  2. {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/pages.yml +1 -1
  3. {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/pre-commit.yml +1 -1
  4. {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/publish-mcp.yml +34 -11
  5. datoon-1.7.2/.github/workflows/publish.yml +82 -0
  6. {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/release.yml +1 -1
  7. {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/tests.yml +2 -2
  8. {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/validate-artifacts.yml +1 -1
  9. {datoon-1.7.0 → datoon-1.7.2}/.github/workflows/workflow-lint.yml +2 -2
  10. {datoon-1.7.0 → datoon-1.7.2}/CHANGELOG.md +14 -0
  11. {datoon-1.7.0 → datoon-1.7.2}/MARKETPLACES.md +16 -10
  12. {datoon-1.7.0 → datoon-1.7.2}/PKG-INFO +1 -1
  13. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/_version.py +3 -3
  14. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/mcp_server.py +5 -0
  15. datoon-1.7.2/src/datoon/readers/_coerce.py +48 -0
  16. datoon-1.7.2/src/datoon/readers/_tabular.py +46 -0
  17. datoon-1.7.2/src/datoon/readers/csv.py +17 -0
  18. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/excel.py +3 -9
  19. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/numbers.py +3 -9
  20. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/xml.py +2 -20
  21. {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/PKG-INFO +1 -1
  22. {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/SOURCES.txt +4 -1
  23. {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/scm_file_list.json +3 -0
  24. datoon-1.7.2/src/datoon.egg-info/scm_version.json +8 -0
  25. {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_readers.py +41 -0
  26. datoon-1.7.2/tests/unit/test_tabular_rows.py +44 -0
  27. {datoon-1.7.0 → datoon-1.7.2}/uv.lock +6 -6
  28. datoon-1.7.0/.github/workflows/publish.yml +0 -45
  29. datoon-1.7.0/src/datoon/readers/csv.py +0 -35
  30. datoon-1.7.0/src/datoon.egg-info/scm_version.json +0 -8
  31. {datoon-1.7.0 → datoon-1.7.2}/.agents/plugins/marketplace.json +0 -0
  32. {datoon-1.7.0 → datoon-1.7.2}/.claude-plugin/marketplace.json +0 -0
  33. {datoon-1.7.0 → datoon-1.7.2}/.claude-plugin/plugin.json +0 -0
  34. {datoon-1.7.0 → datoon-1.7.2}/.editorconfig +0 -0
  35. {datoon-1.7.0 → datoon-1.7.2}/.gitattributes +0 -0
  36. {datoon-1.7.0 → datoon-1.7.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  37. {datoon-1.7.0 → datoon-1.7.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  38. {datoon-1.7.0 → datoon-1.7.2}/.github/dependabot.yml +0 -0
  39. {datoon-1.7.0 → datoon-1.7.2}/.gitignore +0 -0
  40. {datoon-1.7.0 → datoon-1.7.2}/.pre-commit-config.yaml +0 -0
  41. {datoon-1.7.0 → datoon-1.7.2}/.releaserc.yaml +0 -0
  42. {datoon-1.7.0 → datoon-1.7.2}/CLAUDE.md +0 -0
  43. {datoon-1.7.0 → datoon-1.7.2}/CONTRIBUTING.md +0 -0
  44. {datoon-1.7.0 → datoon-1.7.2}/INSTALL.md +0 -0
  45. {datoon-1.7.0 → datoon-1.7.2}/LICENSE +0 -0
  46. {datoon-1.7.0 → datoon-1.7.2}/README.md +0 -0
  47. {datoon-1.7.0 → datoon-1.7.2}/SECURITY.md +0 -0
  48. {datoon-1.7.0 → datoon-1.7.2}/SKILL.md +0 -0
  49. {datoon-1.7.0 → datoon-1.7.2}/assets/datoon-banner.svg +0 -0
  50. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/REPORT.md +0 -0
  51. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/agent_results.json +0 -0
  52. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/expected_answers.json +0 -0
  53. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/generate_payloads.py +0 -0
  54. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/1_large.json +0 -0
  55. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/1_medium.json +0 -0
  56. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/1_small.json +0 -0
  57. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/2_large.json +0 -0
  58. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/2_medium.json +0 -0
  59. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/2_small.json +0 -0
  60. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/3_large.json +0 -0
  61. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/3_medium.json +0 -0
  62. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/payloads/3_small.json +0 -0
  63. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/1_large.report.json +0 -0
  64. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/1_medium.report.json +0 -0
  65. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/1_small.report.json +0 -0
  66. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/2_large.report.json +0 -0
  67. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/2_medium.report.json +0 -0
  68. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/2_small.report.json +0 -0
  69. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/3_large.report.json +0 -0
  70. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/3_medium.report.json +0 -0
  71. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/reports/3_small.report.json +0 -0
  72. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/scored_agent_results.json +0 -0
  73. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/1_large.toon +0 -0
  74. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/1_medium.toon +0 -0
  75. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/1_small.toon +0 -0
  76. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/2_large.toon +0 -0
  77. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/2_medium.toon +0 -0
  78. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/2_small.toon +0 -0
  79. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/3_large.toon +0 -0
  80. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/3_medium.toon +0 -0
  81. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/agent_skill_eval/toon/3_small.toon +0 -0
  82. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/payloads.json +0 -0
  83. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/results/.gitkeep +0 -0
  84. {datoon-1.7.0 → datoon-1.7.2}/benchmarks/run.py +0 -0
  85. {datoon-1.7.0 → datoon-1.7.2}/datoon/SKILL.md +0 -0
  86. {datoon-1.7.0 → datoon-1.7.2}/datoon.skill +0 -0
  87. {datoon-1.7.0 → datoon-1.7.2}/docs/.nojekyll +0 -0
  88. {datoon-1.7.0 → datoon-1.7.2}/docs/app.js +0 -0
  89. {datoon-1.7.0 → datoon-1.7.2}/docs/assets/datoon-banner.svg +0 -0
  90. {datoon-1.7.0 → datoon-1.7.2}/docs/index.html +0 -0
  91. {datoon-1.7.0 → datoon-1.7.2}/docs/styles.css +0 -0
  92. {datoon-1.7.0 → datoon-1.7.2}/glama.json +0 -0
  93. {datoon-1.7.0 → datoon-1.7.2}/install.sh +0 -0
  94. {datoon-1.7.0 → datoon-1.7.2}/plugins/datoon/.codex-plugin/plugin.json +0 -0
  95. {datoon-1.7.0 → datoon-1.7.2}/plugins/datoon/skills/datoon/SKILL.md +0 -0
  96. {datoon-1.7.0 → datoon-1.7.2}/pyproject.toml +0 -0
  97. {datoon-1.7.0 → datoon-1.7.2}/scripts/ab_skill_benchmark.py +0 -0
  98. {datoon-1.7.0 → datoon-1.7.2}/scripts/install.py +0 -0
  99. {datoon-1.7.0 → datoon-1.7.2}/scripts/summarize_agent_skill_eval.py +0 -0
  100. {datoon-1.7.0 → datoon-1.7.2}/scripts/validate_plugin_metadata.py +0 -0
  101. {datoon-1.7.0 → datoon-1.7.2}/scripts/validate_skill_sync.py +0 -0
  102. {datoon-1.7.0 → datoon-1.7.2}/server.json +0 -0
  103. {datoon-1.7.0 → datoon-1.7.2}/setup.cfg +0 -0
  104. {datoon-1.7.0 → datoon-1.7.2}/setup.sh +0 -0
  105. {datoon-1.7.0 → datoon-1.7.2}/skills/datoon/README.md +0 -0
  106. {datoon-1.7.0 → datoon-1.7.2}/skills/datoon/SKILL.md +0 -0
  107. {datoon-1.7.0 → datoon-1.7.2}/smithery.yaml +0 -0
  108. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/__init__.py +0 -0
  109. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/analyzer.py +0 -0
  110. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/cli.py +0 -0
  111. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/converter.py +0 -0
  112. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/models.py +0 -0
  113. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/py.typed +0 -0
  114. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/__init__.py +0 -0
  115. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/columnar.py +0 -0
  116. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/jsonl.py +0 -0
  117. {datoon-1.7.0 → datoon-1.7.2}/src/datoon/readers/yaml.py +0 -0
  118. {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/dependency_links.txt +0 -0
  119. {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/entry_points.txt +0 -0
  120. {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/requires.txt +0 -0
  121. {datoon-1.7.0 → datoon-1.7.2}/src/datoon.egg-info/top_level.txt +0 -0
  122. {datoon-1.7.0 → datoon-1.7.2}/tests/fixtures/users.expected.toon +0 -0
  123. {datoon-1.7.0 → datoon-1.7.2}/tests/fixtures/users.json +0 -0
  124. {datoon-1.7.0 → datoon-1.7.2}/tests/integration/test_toon_cli_integration.py +0 -0
  125. {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_analyzer.py +0 -0
  126. {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_cli.py +0 -0
  127. {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_converter_policy.py +0 -0
  128. {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_installer.py +0 -0
  129. {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_mcp_server.py +0 -0
  130. {datoon-1.7.0 → datoon-1.7.2}/tests/unit/test_models.py +0 -0
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v6
18
+ uses: actions/checkout@v7
19
19
 
20
20
  - name: Setup Python
21
21
  uses: actions/setup-python@v6
@@ -26,7 +26,7 @@ jobs:
26
26
  runs-on: ubuntu-latest
27
27
  steps:
28
28
  - name: Checkout
29
- uses: actions/checkout@v6
29
+ uses: actions/checkout@v7
30
30
 
31
31
  - name: Setup Pages
32
32
  id: setup_pages
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v6
18
+ uses: actions/checkout@v7
19
19
 
20
20
  - name: Setup uv
21
21
  uses: astral-sh/setup-uv@v7
@@ -1,15 +1,18 @@
1
1
  name: Publish to MCP Registry
2
2
 
3
- # Publishes server.json to the official MCP Registry after a GitHub Release.
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
- # Runs after publish.yml has pushed the tagged version to PyPI, so the registry
8
- # can validate the package + ownership token for the released version.
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
- release:
12
- types: [published]
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@v6
29
+ - uses: actions/checkout@v7
30
+ with:
31
+ ref: main
32
+ fetch-depth: 0
24
33
 
25
- - name: Resolve release version
34
+ - name: Resolve version to publish
26
35
  id: version
27
- run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
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 to release tag
30
- if: github.event_name == 'release'
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: github.event_name == 'release'
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@v6
18
+ uses: actions/checkout@v7
19
19
  with:
20
20
  fetch-depth: 0
21
21
 
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - name: Checkout
18
- uses: actions/checkout@v6
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@v6
30
+ uses: actions/checkout@v7
31
31
 
32
32
  - name: Setup uv
33
33
  uses: astral-sh/setup-uv@v7
@@ -18,7 +18,7 @@ jobs:
18
18
  runs-on: ubuntu-latest
19
19
  steps:
20
20
  - name: Checkout
21
- uses: actions/checkout@v6
21
+ uses: actions/checkout@v7
22
22
 
23
23
  - name: Setup Python
24
24
  uses: actions/setup-python@v6
@@ -22,7 +22,7 @@ jobs:
22
22
  runs-on: ubuntu-latest
23
23
  steps:
24
24
  - name: Checkout
25
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
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 on every GitHub Release. It syncs `server.json`'s version to the release
26
- tag, validates, authenticates via **GitHub OIDC** (no stored secret), and runs
27
- `mcp-publisher publish`.
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
- > The ownership token first reaches PyPI with the release that includes this
31
- > change. The publish workflow validates against the released version on PyPI,
32
- > so the first successful registry publish happens on the **next** release after
33
- > this is merged.
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
- Manual publish (if needed):
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datoon
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
4
  Summary: Smart structured-data-to-TOON gateway with pragmatic auto-gating for LLM prompts.
5
5
  Author-email: Andrii Suruhov <andrii.suruhov@gmail.com>
6
6
  License: MIT License
@@ -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.0'
22
- __version_tuple__ = version_tuple = (1, 7, 0)
21
+ __version__ = version = '1.7.2'
22
+ __version_tuple__ = version_tuple = (1, 7, 2)
23
23
 
24
- __commit_id__ = commit_id = 'g50cf0e107'
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
- if not rows:
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
- if not rows:
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datoon
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
4
  Summary: Smart structured-data-to-TOON gateway with pragmatic auto-gating for LLM prompts.
5
5
  Author-email: Andrii Suruhov <andrii.suruhov@gmail.com>
6
6
  License: MIT License
@@ -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",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.7.2",
3
+ "distance": 0,
4
+ "node": "ga011eb92d6c550e4d87db4ebdd3b5efe9cb4f533",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-07"
8
+ }