kpubdata 0.1.0__tar.gz → 0.2.1__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 (141) hide show
  1. kpubdata-0.2.1/.github/workflows/publish-pypi.yml +175 -0
  2. kpubdata-0.2.1/.sisyphus/remove-get-record-plan.md +18 -0
  3. {kpubdata-0.1.0 → kpubdata-0.2.1}/API_SPEC.md +14 -6
  4. {kpubdata-0.1.0 → kpubdata-0.2.1}/ARCHITECTURE.md +106 -1
  5. {kpubdata-0.1.0 → kpubdata-0.2.1}/CANONICAL_MODEL.md +0 -2
  6. {kpubdata-0.1.0 → kpubdata-0.2.1}/CHANGELOG.md +18 -0
  7. {kpubdata-0.1.0 → kpubdata-0.2.1}/PACKAGING.md +49 -5
  8. {kpubdata-0.1.0 → kpubdata-0.2.1}/PKG-INFO +36 -1
  9. {kpubdata-0.1.0 → kpubdata-0.2.1}/PRD.md +2 -12
  10. {kpubdata-0.1.0 → kpubdata-0.2.1}/PROVIDER_ADAPTER_CONTRACT.md +0 -3
  11. {kpubdata-0.1.0 → kpubdata-0.2.1}/README.md +35 -0
  12. kpubdata-0.2.1/SUPPORTED_DATA.md +56 -0
  13. {kpubdata-0.1.0 → kpubdata-0.2.1}/VALIDATION.md +0 -1
  14. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/architecture-diagrams.md +2 -6
  15. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/datago-api-reference.md +3 -3
  16. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/quickstart.md +23 -0
  17. {kpubdata-0.1.0 → kpubdata-0.2.1}/pyproject.toml +1 -1
  18. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/core/dataset.py +32 -20
  19. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/core/models.py +17 -0
  20. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/core/protocol.py +0 -5
  21. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/bok/adapter.py +25 -38
  22. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/datago/adapter.py +44 -51
  23. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/kosis/adapter.py +1 -3
  24. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/lofin/adapter.py +20 -32
  25. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/lofin/catalogue.json +31 -0
  26. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/registry.py +0 -1
  27. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/contract/test_bok.py +0 -5
  28. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/contract/test_datago.py +0 -5
  29. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/contract/test_kosis.py +0 -5
  30. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/contract/test_lofin.py +25 -6
  31. kpubdata-0.2.1/tests/fixtures/lofin/success_fiacrv.json +50 -0
  32. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/test_client_flow.py +1 -22
  33. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/test_exception_propagation.py +1 -41
  34. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/test_lofin_live.py +37 -0
  35. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/core/test_dataset.py +67 -34
  36. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/core/test_dataset_coverage.py +16 -3
  37. kpubdata-0.2.1/tests/unit/core/test_to_pandas.py +82 -0
  38. kpubdata-0.2.1/tests/unit/providers/bok/test_bok_adapter.py +86 -0
  39. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/providers/datago/test_adapter.py +35 -30
  40. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/providers/datago/test_adapter_coverage.py +9 -18
  41. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/providers/datago/test_fixtures.py +7 -4
  42. kpubdata-0.2.1/tests/unit/providers/lofin/test_lofin_adapter.py +85 -0
  43. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_catalog.py +4 -6
  44. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_catalog_coverage.py +0 -3
  45. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_client_coverage.py +0 -6
  46. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_client_transport_requirements.py +0 -6
  47. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_registry.py +6 -7
  48. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_registry_coverage.py +0 -4
  49. {kpubdata-0.1.0 → kpubdata-0.2.1}/uv.lock +1 -1
  50. kpubdata-0.1.0/.github/workflows/publish-pypi.yml +0 -60
  51. kpubdata-0.1.0/SUPPORTED_DATA.md +0 -50
  52. {kpubdata-0.1.0 → kpubdata-0.2.1}/.github/ISSUE_TEMPLATE/new-provider-adapter.yml +0 -0
  53. {kpubdata-0.1.0 → kpubdata-0.2.1}/.github/workflows/ci.yml +0 -0
  54. {kpubdata-0.1.0 → kpubdata-0.2.1}/.github/workflows/integration.yml +0 -0
  55. {kpubdata-0.1.0 → kpubdata-0.2.1}/.gitignore +0 -0
  56. {kpubdata-0.1.0 → kpubdata-0.2.1}/AGENTS.md +0 -0
  57. {kpubdata-0.1.0 → kpubdata-0.2.1}/CONTRIBUTING.md +0 -0
  58. {kpubdata-0.1.0 → kpubdata-0.2.1}/LICENSE +0 -0
  59. {kpubdata-0.1.0 → kpubdata-0.2.1}/ROADMAP.md +0 -0
  60. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/adrs/0001-dialect-inspired-architecture.md +0 -0
  61. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/adrs/0002-standardize-ux-not-native-shape.md +0 -0
  62. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/product-family-architecture.md +0 -0
  63. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/providers/bok.md +0 -0
  64. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/providers/datago.md +0 -0
  65. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/providers/kosis.md +0 -0
  66. {kpubdata-0.1.0 → kpubdata-0.2.1}/docs/tutorial-ai-agent-workflow.md +0 -0
  67. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/__init__.py +0 -0
  68. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/catalog.py +0 -0
  69. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/client.py +0 -0
  70. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/config.py +0 -0
  71. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/core/__init__.py +0 -0
  72. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/core/capability.py +0 -0
  73. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/core/representation.py +0 -0
  74. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/exceptions.py +0 -0
  75. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/__init__.py +0 -0
  76. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/_common.py +0 -0
  77. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/bok/__init__.py +0 -0
  78. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/bok/catalogue.json +0 -0
  79. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/datago/__init__.py +0 -0
  80. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/datago/catalogue.json +0 -0
  81. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/kosis/__init__.py +0 -0
  82. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/kosis/catalogue.json +0 -0
  83. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/providers/lofin/__init__.py +0 -0
  84. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/py.typed +0 -0
  85. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/transport/__init__.py +0 -0
  86. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/transport/decode.py +0 -0
  87. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/transport/http.py +0 -0
  88. {kpubdata-0.1.0 → kpubdata-0.2.1}/src/kpubdata/transport/retry.py +0 -0
  89. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/__init__.py +0 -0
  90. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/contract/__init__.py +0 -0
  91. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/contract/provider_adapter.py +0 -0
  92. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/bok/error_auth.json +0 -0
  93. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/bok/success_empty.json +0 -0
  94. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/bok/success_single_page.json +0 -0
  95. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/error_auth_30.json +0 -0
  96. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/error_invalid_request_10.json +0 -0
  97. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/error_rate_limit_22.json +0 -0
  98. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/error_service_unavailable_01.json +0 -0
  99. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/error_xml_auth_30.xml +0 -0
  100. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_apt_trade.json +0 -0
  101. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_empty.json +0 -0
  102. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_multi_page_1.json +0 -0
  103. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_multi_page_2.json +0 -0
  104. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_single_item.json +0 -0
  105. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_single_page.json +0 -0
  106. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_string_numerics.json +0 -0
  107. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_xml.xml +0 -0
  108. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/datago/success_xml_single_item.xml +0 -0
  109. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/kosis/error_auth.json +0 -0
  110. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/kosis/success_empty.json +0 -0
  111. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/kosis/success_single_page.json +0 -0
  112. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/lofin/error_auth.json +0 -0
  113. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/fixtures/lofin/success_single_page.json +0 -0
  114. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/__init__.py +0 -0
  115. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/conftest.py +0 -0
  116. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/test_bok_live.py +0 -0
  117. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/test_datago_live.py +0 -0
  118. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/test_kosis_live.py +0 -0
  119. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/integration/test_transport_http.py +0 -0
  120. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/__init__.py +0 -0
  121. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/core/__init__.py +0 -0
  122. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/core/test_exceptions.py +0 -0
  123. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/core/test_exceptions_coverage.py +0 -0
  124. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/core/test_models.py +0 -0
  125. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/core/test_models_fixtures.py +0 -0
  126. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/providers/__init__.py +0 -0
  127. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/providers/datago/__init__.py +0 -0
  128. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/providers/datago/conftest.py +0 -0
  129. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_catalogue_validation.py +0 -0
  130. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_config.py +0 -0
  131. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/test_config_coverage.py +0 -0
  132. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/__init__.py +0 -0
  133. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_decode.py +0 -0
  134. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_decode_coverage.py +0 -0
  135. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_http_coverage.py +0 -0
  136. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_http_coverage_extra.py +0 -0
  137. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_logging.py +0 -0
  138. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_retry.py +0 -0
  139. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_retry_after.py +0 -0
  140. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_retry_coverage.py +0 -0
  141. {kpubdata-0.1.0 → kpubdata-0.2.1}/tests/unit/transport/test_transport_requirements.py +0 -0
@@ -0,0 +1,175 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ workflow_dispatch:
8
+ inputs:
9
+ bump:
10
+ description: "Version bump type"
11
+ required: true
12
+ type: choice
13
+ options:
14
+ - patch
15
+ - minor
16
+ - pre
17
+ default: patch
18
+ dry_run:
19
+ description: "Dry run (skip PyPI publish)"
20
+ required: false
21
+ type: boolean
22
+ default: false
23
+
24
+ permissions:
25
+ contents: write
26
+ id-token: write
27
+
28
+ jobs:
29
+ bump-version:
30
+ name: Bump Version
31
+ runs-on: ubuntu-latest
32
+ if: github.event_name == 'workflow_dispatch'
33
+ timeout-minutes: 5
34
+ outputs:
35
+ new_version: ${{ steps.bump.outputs.new_version }}
36
+ tag: ${{ steps.bump.outputs.tag }}
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ with:
40
+ fetch-depth: 0
41
+ token: ${{ secrets.GITHUB_TOKEN }}
42
+
43
+ - name: Configure git
44
+ run: |
45
+ git config user.name "github-actions[bot]"
46
+ git config user.email "github-actions[bot]@users.noreply.github.com"
47
+
48
+ - name: Bump version
49
+ id: bump
50
+ shell: bash
51
+ run: |
52
+ current=$(python3 -c "
53
+ import re, pathlib
54
+ m = re.search(r'version = \"([^\"]+)\"', pathlib.Path('pyproject.toml').read_text())
55
+ print(m.group(1))
56
+ ")
57
+ echo "Current version: ${current}"
58
+
59
+ bump="${{ inputs.bump }}"
60
+ new_version=$(python3 -c "
61
+ import re
62
+ v = '${current}'
63
+ parts = v.split('.')
64
+ major, minor = int(parts[0]), int(parts[1])
65
+ patch_raw = parts[2]
66
+
67
+ if '${bump}' == 'patch':
68
+ patch = int(re.match(r'\d+', patch_raw).group())
69
+ print(f'{major}.{minor}.{patch + 1}')
70
+ elif '${bump}' == 'minor':
71
+ print(f'{major}.{minor + 1}.0')
72
+ elif '${bump}' == 'pre':
73
+ if 'a' in patch_raw:
74
+ patch, pre = patch_raw.split('a')
75
+ print(f'{major}.{minor}.{patch}a{int(pre) + 1}')
76
+ else:
77
+ patch = int(patch_raw)
78
+ print(f'{major}.{minor}.{patch + 1}a0')
79
+ ")
80
+
81
+ python3 -c "
82
+ import pathlib
83
+ p = pathlib.Path('pyproject.toml')
84
+ p.write_text(p.read_text().replace('version = \"${current}\"', 'version = \"${new_version}\"'))
85
+ "
86
+
87
+ echo "new_version=${new_version}" >> "$GITHUB_OUTPUT"
88
+ echo "tag=v${new_version}" >> "$GITHUB_OUTPUT"
89
+ echo "Bumped: ${current} → ${new_version}"
90
+
91
+ - name: Commit and tag
92
+ run: |
93
+ git add pyproject.toml
94
+ git commit -m "chore: bump version to ${{ steps.bump.outputs.new_version }}"
95
+ git tag "${{ steps.bump.outputs.tag }}"
96
+ git push origin main --tags
97
+
98
+ build:
99
+ name: Build Package
100
+ runs-on: ubuntu-latest
101
+ needs: [bump-version]
102
+ if: always() && (needs.bump-version.result == 'success' || needs.bump-version.result == 'skipped')
103
+ timeout-minutes: 10
104
+ steps:
105
+ - uses: actions/checkout@v4
106
+ with:
107
+ ref: ${{ github.event_name == 'workflow_dispatch' && 'main' || github.ref }}
108
+
109
+ - uses: astral-sh/setup-uv@v4
110
+ with:
111
+ enable-cache: true
112
+ cache-dependency-glob: "uv.lock"
113
+
114
+ - uses: actions/setup-python@v5
115
+ with:
116
+ python-version: "3.12"
117
+
118
+ - name: Install dependencies
119
+ run: uv sync --frozen --extra dev
120
+
121
+ - name: Run quality gates
122
+ run: |
123
+ uv run ruff check .
124
+ uv run ruff format --check .
125
+ uv run mypy src
126
+ uv run pytest -ra
127
+
128
+ - name: Build sdist and wheel
129
+ run: uv run python -m build
130
+
131
+ - name: Upload artifacts
132
+ uses: actions/upload-artifact@v4
133
+ with:
134
+ name: dist
135
+ path: dist/
136
+
137
+ create-release:
138
+ name: Create GitHub Release
139
+ runs-on: ubuntu-latest
140
+ needs: [bump-version, build]
141
+ if: github.event_name == 'workflow_dispatch' && !inputs.dry_run
142
+ permissions:
143
+ contents: write
144
+ steps:
145
+ - uses: actions/checkout@v4
146
+ with:
147
+ ref: main
148
+
149
+ - name: Create release
150
+ env:
151
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
152
+ run: |
153
+ gh release create "${{ needs.bump-version.outputs.tag }}" \
154
+ --title "${{ needs.bump-version.outputs.tag }}" \
155
+ --generate-notes \
156
+ --target main
157
+
158
+ publish:
159
+ name: Publish to PyPI
160
+ runs-on: ubuntu-latest
161
+ needs: [bump-version, build]
162
+ if: |
163
+ always() &&
164
+ needs.build.result == 'success' &&
165
+ (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && !inputs.dry_run))
166
+ environment: pypi
167
+ steps:
168
+ - name: Download artifacts
169
+ uses: actions/download-artifact@v4
170
+ with:
171
+ name: dist
172
+ path: dist/
173
+
174
+ - name: Publish to PyPI
175
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,18 @@
1
+ ## Scope
2
+ - Remove `get_record` from the provider protocol, registry validation, dataset binding, tests, and docs.
3
+
4
+ ## Touched modules
5
+ - `src/kpubdata/core/protocol.py`
6
+ - `src/kpubdata/core/dataset.py`
7
+ - `src/kpubdata/registry.py`
8
+ - affected unit/integration/contract tests
9
+ - API and architecture docs describing removed single-record access
10
+
11
+ ## Risks
12
+ - Public `Dataset.get()` removal can leave stale docs/tests behind.
13
+ - Registry validation and protocol changes can break fake adapters in tests.
14
+
15
+ ## Validation steps
16
+ - Run LSP diagnostics on changed source and test files.
17
+ - Run `uv run pytest`.
18
+ - Run `uv run mypy src`.
@@ -43,12 +43,21 @@ dataset = client.dataset("molit.apartment_trades")
43
43
  result = dataset.list(lawd_code="11680", deal_ym="202503")
44
44
  ```
45
45
 
46
- ### Single record
46
+ `list()` returns exactly one page of results. When another page is available,
47
+ the returned `RecordBatch.next_page` is set.
48
+
49
+ ### List all pages
47
50
 
48
51
  ```python
49
- record = dataset.get(id="...")
52
+ dataset = client.dataset("molit.apartment_trades")
53
+ for batch in dataset.list_all(lawd_code="11680", deal_ym="202503"):
54
+ for item in batch.items:
55
+ print(item)
50
56
  ```
51
57
 
58
+ `list_all()` yields one `RecordBatch` per page and follows `next_page`
59
+ automatically until pagination is exhausted.
60
+
52
61
  ### Schema
53
62
 
54
63
  ```python
@@ -82,9 +91,9 @@ Rules:
82
91
 
83
92
  Returns `RecordBatch`.
84
93
 
85
- ### `get()`
94
+ ### `list_all()`
86
95
 
87
- Returns one normalized record or `None`.
96
+ Returns a generator of `RecordBatch` values, one per page.
88
97
 
89
98
  ### `schema()`
90
99
 
@@ -101,7 +110,7 @@ KPubData promises stability for:
101
110
  - `Client`
102
111
  - `Client.from_env()`
103
112
  - dataset discovery methods
104
- - `Dataset.list/get/schema/call_raw`
113
+ - `Dataset.list/list_all/schema/call_raw`
105
114
  - canonical model classes
106
115
  - canonical error types
107
116
 
@@ -157,4 +166,3 @@ client.call_provider_endpoint("seoul", "SearchSTNTimeTableByIDService", ...)
157
166
  | :--- | :--- | :--- |
158
167
  | [kpubdata-builder](https://github.com/yeongseon/kpubdata-builder) | [API_CONTRACT.md](https://github.com/yeongseon/kpubdata-builder/blob/main/API_CONTRACT.md) | Builder API 규약 |
159
168
  | [kpubdata-studio](https://github.com/yeongseon/kpubdata-studio) | [API_CONTRACT.md](https://github.com/yeongseon/kpubdata-studio/blob/main/API_CONTRACT.md) | Studio API 규약 |
160
-
@@ -144,7 +144,112 @@ KPubData는 이 모든 은행을 대신 처리해주는 **"통합 키오스크"*
144
144
  5. **5단계: 결과 반환 (Return)**
145
145
  - 최종적으로 예쁘게 포장된 `RecordBatch` 객체가 사용자에게 전달됩니다.
146
146
 
147
- ## 6. 자주 묻는 질문 (FAQ)
147
+ ## 6. 인증 흐름 (Authentication Flow)
148
+
149
+ ### 6.1 개요
150
+
151
+ 공공데이터 API를 사용하려면 각 기관에서 발급한 **인증키(API Key)**가 필요합니다. KPubData는 이 키를 사용자로부터 받아 각 기관이 요구하는 방식으로 HTTP 요청에 주입합니다.
152
+
153
+ 핵심 설계 원칙:
154
+ - **키 저장은 `KPubDataConfig`에 집중**: 모든 기관의 키를 한 곳에서 관리합니다.
155
+ - **키 주입은 각 어댑터에 위임**: 기관마다 키를 넣는 위치와 파라미터 이름이 다르므로, 이 로직은 어댑터가 담당합니다.
156
+ - **환경변수 우선 해석**: 명시적으로 전달된 키 → `KPUBDATA_*` 환경변수 → fallback 환경변수 순으로 탐색합니다.
157
+
158
+ ### 6.2 키 해석 우선순위
159
+
160
+ ```mermaid
161
+ flowchart TD
162
+ Start[키 요청: require_provider_key] --> E1{명시적으로 전달된 키?}
163
+ E1 -- 있음 --> Use[키 반환]
164
+ E1 -- 없음 --> E2{KPUBDATA_{PROVIDER}_API_KEY 환경변수?}
165
+ E2 -- 있음 --> Use
166
+ E2 -- 없음 --> E3{"{PROVIDER}_API_KEY" 환경변수?}
167
+ E3 -- 있음 --> Use
168
+ E3 -- 없음 --> Err[ConfigError 발생]
169
+ ```
170
+
171
+ ```python
172
+ # 1순위: 명시적 전달
173
+ client = Client(provider_keys={"datago": "MY_KEY"})
174
+
175
+ # 2순위: KPUBDATA_ 접두사 환경변수
176
+ # export KPUBDATA_DATAGO_API_KEY="MY_KEY"
177
+ client = Client.from_env()
178
+
179
+ # 3순위: 접두사 없는 환경변수 (fallback)
180
+ # export DATAGO_API_KEY="MY_KEY"
181
+ client = Client.from_env()
182
+ ```
183
+
184
+ **관련 코드**: `src/kpubdata/config.py` → `KPubDataConfig.get_provider_key()`, `require_provider_key()`
185
+
186
+ ### 6.3 키의 내부 전달 경로
187
+
188
+ ```mermaid
189
+ sequenceDiagram
190
+ participant U as 사용자
191
+ participant C as Client
192
+ participant Cfg as KPubDataConfig
193
+ participant A as ProviderAdapter
194
+ participant T as HttpTransport
195
+ participant P as 공공 API
196
+
197
+ U->>C: Client(provider_keys={"datago": "KEY"})
198
+ C->>Cfg: KPubDataConfig(provider_keys={...})
199
+ Note over Cfg: 키를 dict에 보관
200
+
201
+ U->>C: dataset("datago.village_fcst").list(...)
202
+ C->>A: query_records(dataset_ref, query)
203
+ A->>Cfg: require_provider_key("datago")
204
+ Cfg-->>A: "KEY" 반환
205
+ A->>A: _build_params()에서 키를 HTTP 파라미터에 주입
206
+ A->>T: HTTP 요청 (키 포함)
207
+ T->>P: GET https://apis.data.go.kr/...?serviceKey=KEY
208
+ P-->>T: 응답
209
+ ```
210
+
211
+ **핵심 메서드**: 모든 어댑터는 `_require_api_key()` → `self._config.require_provider_key("slug")` 패턴을 사용합니다.
212
+
213
+ ### 6.4 기관별 키 주입 방식
214
+
215
+ 기관마다 키를 HTTP 요청에 넣는 위치와 파라미터 이름이 다릅니다.
216
+
217
+ | Provider | 파라미터 이름 | 주입 위치 | 예시 | 특이사항 |
218
+ |---|---|---|---|---|
219
+ | `datago` | `serviceKey` | Query parameter | `?serviceKey=KEY` | dataset별 `service_key_param` 메타데이터로 파라미터명 오버라이드 가능 |
220
+ | `bok` | _(URL 경로)_ | URL path segment | `/{KEY}/json/{operation}/...` | 쿼리 파라미터가 아닌 **URL 경로**에 직접 삽입 |
221
+ | `kosis` | `apiKey` | Query parameter | `?apiKey=KEY` | 표준적인 쿼리 파라미터 방식 |
222
+ | `lofin` | `Key` | Query parameter | `?Key=KEY` | 대문자 `K` 필수. `Type=json`도 대문자 `T` 필수 |
223
+
224
+ ```text
225
+ datago: GET https://apis.data.go.kr/.../getVilageFcst?serviceKey=KEY&...
226
+ bok: GET https://ecos.bok.or.kr/api/KEY/json/StatisticSearch/...
227
+ kosis: GET https://kosis.kr/openapi/Idx/indikatorList.do?apiKey=KEY&...
228
+ lofin: GET https://www.lofin365.go.kr/lf/hub/AJGCF?Key=KEY&Type=json&...
229
+ ```
230
+
231
+ ### 6.5 새 어댑터 추가 시 인증 구현 가이드
232
+
233
+ 새로운 데이터 기관을 추가할 때, 인증 관련 구현은 다음 3단계를 따릅니다.
234
+
235
+ **1단계**: `_require_api_key()` 메서드를 정의합니다.
236
+ ```python
237
+ def _require_api_key(self) -> str:
238
+ return self._config.require_provider_key("your_provider_slug")
239
+ ```
240
+
241
+ **2단계**: 요청 생성 시 키를 해당 기관의 방식에 맞게 주입합니다.
242
+ ```python
243
+ # Query parameter 방식 (가장 일반적)
244
+ params = {"apiKey": self._require_api_key(), ...}
245
+
246
+ # URL path 방식 (bok처럼 경로에 넣는 경우)
247
+ url = f"{base_url}/{self._require_api_key()}/json/{operation}"
248
+ ```
249
+
250
+ **3단계**: `Client`의 `_BUILTIN_PROVIDERS` 튜플에 등록하면, `from_env()`가 자동으로 `KPUBDATA_{SLUG}_API_KEY` 환경변수를 스캔합니다.
251
+
252
+ ## 7. 자주 묻는 질문 (FAQ)
148
253
 
149
254
  **Q: 새 데이터셋을 추가하려면 어디를 수정하나요?**
150
255
  A: 해당 기관의 어댑터(`providers/<provider>/adapter.py`)와 데이터 목록 파일(`catalogue.json`)을 수정하면 됩니다.
@@ -312,7 +312,6 @@ class Dataset:
312
312
  ref: DatasetRef
313
313
 
314
314
  def list(self, **filters) -> RecordBatch: ...
315
- def get(self, **key) -> dict[str, object] | None: ...
316
315
  def schema(self) -> SchemaDescriptor | None: ...
317
316
  def call_raw(self, operation: str, **params) -> object: ...
318
317
  ```
@@ -352,4 +351,3 @@ Do not erase provider-native detail.
352
351
  | [PROVIDER_ADAPTER_CONTRACT.md](./PROVIDER_ADAPTER_CONTRACT.md) | 어댑터 구현 규약 |
353
352
  | [API_SPEC.md](./API_SPEC.md) | 파이썬 API 명세 |
354
353
  | [VALIDATION.md](./VALIDATION.md) | 아키텍처 타당성 검증 |
355
-
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2025-04-17
11
+
12
+ ### Added
13
+ - Single-page pagination contract for all adapters (datago, bok, lofin, kosis)
14
+ - `Dataset.list_all()` generator for automatic multi-page iteration
15
+ - `RecordBatch.to_pandas()` for pandas DataFrame conversion (optional `pandas` dependency)
16
+ - Unit tests for BOK and LOFIN adapters
17
+
18
+ ### Changed
19
+ - Default `page_size` increased from 10 to 100
20
+ - **Breaking**: `query_records()` now returns a single page instead of auto-draining all pages
21
+
22
+ ### Removed
23
+ - Unreachable single-record adapter stubs
24
+ - Single-record access from the provider and dataset public APIs
25
+
26
+ ## [0.1.0] - 2025-04-10
27
+
10
28
  ### Added
11
29
 
12
30
  - Core framework: `Client`, `Dataset`, `Catalog`, `Query`, `RecordBatch` public API
@@ -82,11 +82,55 @@ uv run python -m build
82
82
 
83
83
  ### Release
84
84
 
85
- 1. bump version
86
- 2. update changelog
87
- 3. run full quality gates
88
- 4. build sdist + wheel
89
- 5. publish to TestPyPI/PyPI
85
+ GitHub Actions `Publish to PyPI` workflow를 통해 배포합니다.
86
+
87
+ #### 방법 1: 수동 트리거 (권장)
88
+
89
+ 1. GitHub → Actions → **Publish to PyPI** → **Run workflow**
90
+ 2. 옵션 선택:
91
+ - **bump**: `patch` (0.1.0 → 0.1.1), `minor` (0.1.0 → 0.2.0), `pre` (0.1.0 → 0.1.1a0)
92
+ - **dry_run**: 체크하면 PyPI 배포 없이 빌드만 확인
93
+ 3. workflow가 자동으로 수행하는 작업:
94
+ - `pyproject.toml` 버전 bump + commit + tag
95
+ - quality gates 실행 (ruff, mypy, pytest)
96
+ - sdist + wheel 빌드
97
+ - GitHub Release 생성 (자동 release notes)
98
+ - PyPI 배포 (trusted publisher / OIDC)
99
+
100
+ #### 방법 2: GitHub Release 수동 생성
101
+
102
+ 1. 직접 `pyproject.toml` 버전 수정 + commit + push
103
+ 2. GitHub → Releases → **Create a new release**
104
+ 3. 태그: `v{version}` (예: `v0.2.0`)
105
+ 4. Release 생성 시 workflow가 자동으로 빌드 + PyPI 배포
106
+
107
+ #### 배포 전 체크리스트
108
+
109
+ - [ ] main 브랜치에 모든 변경사항 merge 완료
110
+ - [ ] CI 통과 확인 (ruff, mypy, pytest)
111
+ - [ ] `SUPPORTED_DATA.md` 최신 상태
112
+
113
+ #### 배포 흐름도
114
+
115
+ ```mermaid
116
+ flowchart TD
117
+ A[변경사항 main에 merge] --> B{배포 방식}
118
+ B -->|수동 트리거| C[Actions > Run workflow]
119
+ C --> D[bump type 선택]
120
+ D --> E[자동: 버전 bump + commit + tag]
121
+ B -->|Release 생성| F[수동: 버전 수정 + Release 생성]
122
+ E --> G[Quality gates]
123
+ F --> G
124
+ G --> H[Build sdist + wheel]
125
+ H --> I[GitHub Release 생성]
126
+ I --> J[PyPI 배포]
127
+ ```
128
+
129
+ #### 환경 설정
130
+
131
+ - **PyPI trusted publisher**: GitHub Actions OIDC — 별도 API 토큰 불필요
132
+ - **GitHub environment**: `pypi` (Settings → Environments)
133
+ - **Workflow 파일**: `.github/workflows/publish-pypi.yml`
90
134
 
91
135
  ## 7. Versioning policy
92
136
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kpubdata
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Dialect-inspired Python access framework for Korean public data
5
5
  Author: Yeongseon Choe
6
6
  License: MIT
@@ -297,6 +297,37 @@ for item in result.items[:5]:
297
297
  # ...
298
298
  ```
299
299
 
300
+ ### 9. 전체 페이지 자동 조회 (list_all)
301
+
302
+ 대량의 데이터를 페이지 단위로 자동 순회하려면 `list_all()`을 사용합니다.
303
+
304
+ ```python
305
+ ds = client.dataset("lofin.expenditure_budget")
306
+
307
+ # 모든 페이지를 자동으로 순회하며 RecordBatch를 반환
308
+ for batch in ds.list_all(fyr="2024"):
309
+ print(f"이번 페이지: {len(batch.items)}건")
310
+ for item in batch.items:
311
+ print(item)
312
+ ```
313
+
314
+ ### 10. pandas DataFrame 변환
315
+
316
+ 조회 결과를 pandas DataFrame으로 변환하여 분석할 수 있습니다.
317
+
318
+ ```bash
319
+ pip install kpubdata[pandas]
320
+ ```
321
+
322
+ ```python
323
+ ds = client.dataset("bok.base_rate")
324
+ result = ds.list(start_date="202401", end_date="202412")
325
+
326
+ # pandas DataFrame으로 변환
327
+ df = result.to_pandas()
328
+ print(df.head())
329
+ ```
330
+
300
331
  ## 지원 중인 공공데이터
301
332
 
302
333
  현재 지원 현황의 상세 정보와 진행 예정 항목은 [SUPPORTED_DATA.md](./SUPPORTED_DATA.md)에서 확인할 수 있습니다.
@@ -316,6 +347,9 @@ for item in result.items[:5]:
316
347
  | 지방재정365 (`lofin`) | 기능별세출 (`expenditure_function`) | 지원 |
317
348
  | 지방재정365 (`lofin`) | 채무비율현황 (`debt_ratio`) | 지원 |
318
349
  | 지방재정365 (`lofin`) | 재정자립도현황 (`fiscal_independence`) | 지원 |
350
+ | 지방재정365 (`lofin`) | 재원별 세입결산 (`revenue_by_source`) | 지원 |
351
+
352
+ > 검증 수준 및 실API 최종 검증일 등 상세 정보는 [SUPPORTED_DATA.md](./SUPPORTED_DATA.md)를 참고하세요.
319
353
 
320
354
  ## 문서 가이드 (Document Guide)
321
355
 
@@ -378,3 +412,4 @@ KPubData의 설계 철학과 사용 방법을 안내하는 문서 목록입니
378
412
 
379
413
  ### v0.3
380
414
  - 표준 코어를 기반으로 한 경량 [MCP](https://modelcontextprotocol.io/)(Model Context Protocol — AI 모델이 외부 도구를 사용하는 표준 규약) 어댑터 지원
415
+
@@ -157,16 +157,7 @@ Acceptance:
157
157
  - includes `raw`
158
158
  - includes metadata about pagination and provider
159
159
 
160
- ### FR-6. Single-record access
161
-
162
- Supported datasets may expose a single-record get operation.
163
-
164
- Acceptance:
165
-
166
- - `dataset.get(...)`
167
- - unsupported datasets raise `UnsupportedCapabilityError`
168
-
169
- ### FR-7. Schema/metadata access
160
+ ### FR-6. Schema/metadata access
170
161
 
171
162
  Datasets may expose schema or field metadata.
172
163
 
@@ -175,7 +166,7 @@ Acceptance:
175
166
  - `dataset.schema()`
176
167
  - returns `SchemaDescriptor | None`
177
168
 
178
- ### FR-8. Raw access
169
+ ### FR-7. Raw access
179
170
 
180
171
  Every provider adapter must expose a raw-call path.
181
172
 
@@ -303,4 +294,3 @@ client.getRTMSDataSvcAptTradeDev(...)
303
294
  | :--- | :--- | :--- |
304
295
  | [kpubdata-builder](https://github.com/yeongseon/kpubdata-builder) | [PRD.md](https://github.com/yeongseon/kpubdata-builder/blob/main/PRD.md) | Builder 제품 요구사항 |
305
296
  | [kpubdata-studio](https://github.com/yeongseon/kpubdata-studio) | [PRD.md](https://github.com/yeongseon/kpubdata-studio/blob/main/PRD.md) | Studio 제품 요구사항 |
306
-
@@ -149,7 +149,6 @@ class ProviderAdapter(Protocol):
149
149
  def search_datasets(self, text: str) -> list[DatasetRef]: ...
150
150
  def get_dataset(self, dataset_id: str) -> DatasetRef: ...
151
151
  def query_records(self, dataset: DatasetRef, query: Query) -> RecordBatch: ...
152
- def get_record(self, dataset: DatasetRef, key: dict[str, object]) -> dict[str, object] | None: ...
153
152
  def get_schema(self, dataset: DatasetRef) -> SchemaDescriptor | None: ...
154
153
  def call_raw(self, dataset: DatasetRef, operation: str, params: dict[str, object]) -> object: ...
155
154
  ```
@@ -163,7 +162,6 @@ classDiagram
163
162
  +search_datasets(text: str) list~DatasetRef~
164
163
  +get_dataset(dataset_id: str) DatasetRef
165
164
  +query_records(dataset: DatasetRef, query: Query) RecordBatch
166
- +get_record(dataset: DatasetRef, key: dict) dict
167
165
  +get_schema(dataset: DatasetRef) SchemaDescriptor
168
166
  +call_raw(dataset: DatasetRef, op: str, params: dict) object
169
167
  }
@@ -248,4 +246,3 @@ Otherwise, keep the complexity local to the adapter.
248
246
  | [API_SPEC.md](./API_SPEC.md) | 파이썬 API 명세 |
249
247
  | [VALIDATION.md](./VALIDATION.md) | 아키텍처 타당성 검증 |
250
248
  | [AGENTS.md](./AGENTS.md) | 어댑터 개발 가이드 및 에이전트 규칙 |
251
-
@@ -260,6 +260,37 @@ for item in result.items[:5]:
260
260
  # ...
261
261
  ```
262
262
 
263
+ ### 9. 전체 페이지 자동 조회 (list_all)
264
+
265
+ 대량의 데이터를 페이지 단위로 자동 순회하려면 `list_all()`을 사용합니다.
266
+
267
+ ```python
268
+ ds = client.dataset("lofin.expenditure_budget")
269
+
270
+ # 모든 페이지를 자동으로 순회하며 RecordBatch를 반환
271
+ for batch in ds.list_all(fyr="2024"):
272
+ print(f"이번 페이지: {len(batch.items)}건")
273
+ for item in batch.items:
274
+ print(item)
275
+ ```
276
+
277
+ ### 10. pandas DataFrame 변환
278
+
279
+ 조회 결과를 pandas DataFrame으로 변환하여 분석할 수 있습니다.
280
+
281
+ ```bash
282
+ pip install kpubdata[pandas]
283
+ ```
284
+
285
+ ```python
286
+ ds = client.dataset("bok.base_rate")
287
+ result = ds.list(start_date="202401", end_date="202412")
288
+
289
+ # pandas DataFrame으로 변환
290
+ df = result.to_pandas()
291
+ print(df.head())
292
+ ```
293
+
263
294
  ## 지원 중인 공공데이터
264
295
 
265
296
  현재 지원 현황의 상세 정보와 진행 예정 항목은 [SUPPORTED_DATA.md](./SUPPORTED_DATA.md)에서 확인할 수 있습니다.
@@ -279,6 +310,9 @@ for item in result.items[:5]:
279
310
  | 지방재정365 (`lofin`) | 기능별세출 (`expenditure_function`) | 지원 |
280
311
  | 지방재정365 (`lofin`) | 채무비율현황 (`debt_ratio`) | 지원 |
281
312
  | 지방재정365 (`lofin`) | 재정자립도현황 (`fiscal_independence`) | 지원 |
313
+ | 지방재정365 (`lofin`) | 재원별 세입결산 (`revenue_by_source`) | 지원 |
314
+
315
+ > 검증 수준 및 실API 최종 검증일 등 상세 정보는 [SUPPORTED_DATA.md](./SUPPORTED_DATA.md)를 참고하세요.
282
316
 
283
317
  ## 문서 가이드 (Document Guide)
284
318
 
@@ -341,3 +375,4 @@ KPubData의 설계 철학과 사용 방법을 안내하는 문서 목록입니
341
375
 
342
376
  ### v0.3
343
377
  - 표준 코어를 기반으로 한 경량 [MCP](https://modelcontextprotocol.io/)(Model Context Protocol — AI 모델이 외부 도구를 사용하는 표준 규약) 어댑터 지원
378
+