kpubdata 0.1.1__tar.gz → 0.2.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 (141) hide show
  1. kpubdata-0.2.2/.sisyphus/remove-get-record-plan.md +18 -0
  2. {kpubdata-0.1.1 → kpubdata-0.2.2}/API_SPEC.md +14 -6
  3. {kpubdata-0.1.1 → kpubdata-0.2.2}/ARCHITECTURE.md +86 -1
  4. {kpubdata-0.1.1 → kpubdata-0.2.2}/CANONICAL_MODEL.md +0 -2
  5. {kpubdata-0.1.1 → kpubdata-0.2.2}/CHANGELOG.md +18 -0
  6. {kpubdata-0.1.1 → kpubdata-0.2.2}/PKG-INFO +36 -1
  7. {kpubdata-0.1.1 → kpubdata-0.2.2}/PRD.md +2 -12
  8. {kpubdata-0.1.1 → kpubdata-0.2.2}/PROVIDER_ADAPTER_CONTRACT.md +9 -4
  9. {kpubdata-0.1.1 → kpubdata-0.2.2}/README.md +35 -0
  10. kpubdata-0.2.2/SUPPORTED_DATA.md +56 -0
  11. {kpubdata-0.1.1 → kpubdata-0.2.2}/VALIDATION.md +0 -1
  12. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/architecture-diagrams.md +2 -6
  13. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/datago-api-reference.md +3 -3
  14. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/quickstart.md +23 -0
  15. {kpubdata-0.1.1 → kpubdata-0.2.2}/pyproject.toml +1 -1
  16. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/dataset.py +37 -20
  17. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/models.py +17 -0
  18. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/protocol.py +0 -5
  19. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/bok/adapter.py +25 -38
  20. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/datago/adapter.py +44 -51
  21. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/kosis/adapter.py +1 -3
  22. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/lofin/adapter.py +20 -32
  23. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/registry.py +0 -1
  24. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/test_bok.py +0 -5
  25. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/test_datago.py +0 -5
  26. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/test_kosis.py +0 -5
  27. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/test_lofin.py +1 -6
  28. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_client_flow.py +1 -22
  29. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_exception_propagation.py +1 -41
  30. kpubdata-0.2.2/tests/unit/core/test_dataset.py +209 -0
  31. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_dataset_coverage.py +16 -3
  32. kpubdata-0.2.2/tests/unit/core/test_to_pandas.py +110 -0
  33. kpubdata-0.2.2/tests/unit/providers/bok/test_bok_adapter.py +86 -0
  34. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/test_adapter.py +35 -30
  35. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/test_adapter_coverage.py +9 -18
  36. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/test_fixtures.py +7 -4
  37. kpubdata-0.2.2/tests/unit/providers/lofin/test_lofin_adapter.py +85 -0
  38. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_catalog.py +4 -6
  39. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_catalog_coverage.py +0 -3
  40. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_client_coverage.py +0 -6
  41. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_client_transport_requirements.py +0 -6
  42. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_registry.py +6 -7
  43. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_registry_coverage.py +0 -4
  44. {kpubdata-0.1.1 → kpubdata-0.2.2}/uv.lock +1 -1
  45. kpubdata-0.1.1/SUPPORTED_DATA.md +0 -51
  46. kpubdata-0.1.1/tests/unit/core/test_dataset.py +0 -139
  47. {kpubdata-0.1.1 → kpubdata-0.2.2}/.github/ISSUE_TEMPLATE/new-provider-adapter.yml +0 -0
  48. {kpubdata-0.1.1 → kpubdata-0.2.2}/.github/workflows/ci.yml +0 -0
  49. {kpubdata-0.1.1 → kpubdata-0.2.2}/.github/workflows/integration.yml +0 -0
  50. {kpubdata-0.1.1 → kpubdata-0.2.2}/.github/workflows/publish-pypi.yml +0 -0
  51. {kpubdata-0.1.1 → kpubdata-0.2.2}/.gitignore +0 -0
  52. {kpubdata-0.1.1 → kpubdata-0.2.2}/AGENTS.md +0 -0
  53. {kpubdata-0.1.1 → kpubdata-0.2.2}/CONTRIBUTING.md +0 -0
  54. {kpubdata-0.1.1 → kpubdata-0.2.2}/LICENSE +0 -0
  55. {kpubdata-0.1.1 → kpubdata-0.2.2}/PACKAGING.md +0 -0
  56. {kpubdata-0.1.1 → kpubdata-0.2.2}/ROADMAP.md +0 -0
  57. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/adrs/0001-dialect-inspired-architecture.md +0 -0
  58. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/adrs/0002-standardize-ux-not-native-shape.md +0 -0
  59. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/product-family-architecture.md +0 -0
  60. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/providers/bok.md +0 -0
  61. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/providers/datago.md +0 -0
  62. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/providers/kosis.md +0 -0
  63. {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/tutorial-ai-agent-workflow.md +0 -0
  64. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/__init__.py +0 -0
  65. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/catalog.py +0 -0
  66. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/client.py +0 -0
  67. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/config.py +0 -0
  68. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/__init__.py +0 -0
  69. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/capability.py +0 -0
  70. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/representation.py +0 -0
  71. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/exceptions.py +0 -0
  72. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/__init__.py +0 -0
  73. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/_common.py +0 -0
  74. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/bok/__init__.py +0 -0
  75. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/bok/catalogue.json +0 -0
  76. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/datago/__init__.py +0 -0
  77. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/datago/catalogue.json +0 -0
  78. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/kosis/__init__.py +0 -0
  79. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/kosis/catalogue.json +0 -0
  80. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/lofin/__init__.py +0 -0
  81. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/lofin/catalogue.json +0 -0
  82. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/py.typed +0 -0
  83. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/transport/__init__.py +0 -0
  84. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/transport/decode.py +0 -0
  85. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/transport/http.py +0 -0
  86. {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/transport/retry.py +0 -0
  87. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/__init__.py +0 -0
  88. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/__init__.py +0 -0
  89. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/provider_adapter.py +0 -0
  90. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/bok/error_auth.json +0 -0
  91. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/bok/success_empty.json +0 -0
  92. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/bok/success_single_page.json +0 -0
  93. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_auth_30.json +0 -0
  94. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_invalid_request_10.json +0 -0
  95. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_rate_limit_22.json +0 -0
  96. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_service_unavailable_01.json +0 -0
  97. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_xml_auth_30.xml +0 -0
  98. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_apt_trade.json +0 -0
  99. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_empty.json +0 -0
  100. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_multi_page_1.json +0 -0
  101. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_multi_page_2.json +0 -0
  102. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_single_item.json +0 -0
  103. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_single_page.json +0 -0
  104. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_string_numerics.json +0 -0
  105. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_xml.xml +0 -0
  106. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_xml_single_item.xml +0 -0
  107. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/kosis/error_auth.json +0 -0
  108. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/kosis/success_empty.json +0 -0
  109. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/kosis/success_single_page.json +0 -0
  110. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/lofin/error_auth.json +0 -0
  111. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/lofin/success_fiacrv.json +0 -0
  112. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/lofin/success_single_page.json +0 -0
  113. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/__init__.py +0 -0
  114. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/conftest.py +0 -0
  115. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_bok_live.py +0 -0
  116. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_datago_live.py +0 -0
  117. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_kosis_live.py +0 -0
  118. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_lofin_live.py +0 -0
  119. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_transport_http.py +0 -0
  120. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/__init__.py +0 -0
  121. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/__init__.py +0 -0
  122. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_exceptions.py +0 -0
  123. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_exceptions_coverage.py +0 -0
  124. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_models.py +0 -0
  125. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_models_fixtures.py +0 -0
  126. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/__init__.py +0 -0
  127. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/__init__.py +0 -0
  128. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/conftest.py +0 -0
  129. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_catalogue_validation.py +0 -0
  130. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_config.py +0 -0
  131. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_config_coverage.py +0 -0
  132. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/__init__.py +0 -0
  133. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_decode.py +0 -0
  134. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_decode_coverage.py +0 -0
  135. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_http_coverage.py +0 -0
  136. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_http_coverage_extra.py +0 -0
  137. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_logging.py +0 -0
  138. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_retry.py +0 -0
  139. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_retry_after.py +0 -0
  140. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_retry_coverage.py +0 -0
  141. {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_transport_requirements.py +0 -0
@@ -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,92 @@ 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
+ ## 7. 페이지네이션 전략 (Pagination Strategy)
214
+
215
+ KPubData는 대량의 데이터를 효율적으로 가져오기 위해 두 가지 페이지네이션 전략을 사용합니다.
216
+
217
+ ### 7.1 전략 종류
218
+ - **오프셋 기반 (Offset-based)**: 페이지 번호를 이용해 데이터를 요청합니다. `next_page` 값이 반환됩니다.
219
+ - **커서 기반 (Cursor-based)**: 특정 지점을 가리키는 포인터(커서)를 이용해 다음 데이터를 요청합니다. `next_cursor` 값이 반환됩니다.
220
+
221
+ ### 7.2 동작 원리 및 휴리스틱 (Heuristic)
222
+ 어댑터는 가용한 정보에 따라 다음과 같이 다음 페이지 존재 여부를 결정합니다.
223
+ 1. **정밀 계산**: `total_count` 정보를 알 수 있는 경우(예: bok, lofin), 현재까지 가져온 개수와 전체 개수를 비교해 `next_page`를 정확히 계산합니다.
224
+ 2. **Best-effort 휴리스틱**: 전체 개수 정보가 없는 경우(예: datago), `len(items) == page_size` 공식을 사용합니다.
225
+ - 현재 페이지의 아이템 개수가 요청한 페이지 크기와 같다면, 다음 페이지가 더 있을 것으로 가정합니다.
226
+ - 이 방식은 마지막 데이터가 딱 페이지 크기에 맞춰 끝날 경우, 실제로 데이터가 없는 다음 페이지를 한 번 더 호출하는 **추가 fetch(Extra empty fetch)**가 발생할 수 있습니다. 이는 문서화된 트레이드오프(trade-off)입니다.
227
+
228
+ ### 7.3 list_all()의 처리
229
+ 사용자가 `list_all()` 메서드를 호출하면, KPubData는 어댑터가 반환하는 전략(`next_cursor` 우선, 없으면 `next_page`)을 자동으로 판단하여 모든 데이터를 순회합니다.
230
+
231
+ ## 8. 자주 묻는 질문 (FAQ)
232
+
148
233
 
149
234
  **Q: 새 데이터셋을 추가하려면 어디를 수정하나요?**
150
235
  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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kpubdata
3
- Version: 0.1.1
3
+ Version: 0.2.2
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,13 +162,20 @@ 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
  }
170
168
  ```
171
169
 
172
- ## 4. Capability rules
170
+ ## 4. 페이지네이션 반환 규약 (Pagination Return Contract)
171
+
172
+ 어댑터는 `query_records` 결과로 반환되는 `RecordBatch`에 다음 페이지 정보를 포함해야 합니다.
173
+
174
+ - **`next_page` 또는 `next_cursor` 반환**: 어댑터는 반드시 둘 중 하나를 반환하거나, 마지막 페이지인 경우 둘 다 `None`을 반환해야 합니다.
175
+ - **우선순위**: `list_all()`은 `next_cursor`가 존재할 경우 이를 우선적으로 사용하며, 없을 경우 `next_page`를 사용합니다.
176
+ - **권장 방식**: 가급적 `total_count` 기반의 정밀한 계산 방식을 선호합니다. 하지만 전체 개수 정보를 알 수 없는 경우 `len(items) == page_size` 휴리스틱을 사용하는 것도 허용됩니다.
177
+
178
+ ## 5. Capability rules
173
179
 
174
180
  - declare only what the adapter truly supports
175
181
  - if an operation is unavailable, raise `UnsupportedCapabilityError`
@@ -248,4 +254,3 @@ Otherwise, keep the complexity local to the adapter.
248
254
  | [API_SPEC.md](./API_SPEC.md) | 파이썬 API 명세 |
249
255
  | [VALIDATION.md](./VALIDATION.md) | 아키텍처 타당성 검증 |
250
256
  | [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
+
@@ -0,0 +1,56 @@
1
+ # 지원 공공데이터 현황
2
+
3
+ 이 문서는 KPubData가 지원하는 공공데이터 목록과 진행 현황을 관리하는 기준 문서입니다.
4
+
5
+ > **상태 정의**
6
+ >
7
+ > - 지원: 구현 완료 + fixture/unit/contract 테스트 통과
8
+ > - 진행 중: 구현 중이지만 아직 테스트가 완료되지 않음
9
+ > - 예정: 후보 단계 (이슈 등록 또는 아이디어)
10
+ >
11
+ > **검증 정의**
12
+ >
13
+ > - 테스트 검증: fixture 기반 unit 테스트 + contract 테스트 통과
14
+ > - 실API 검증: 위 조건 + 실 API integration 테스트 통과 ([#80](https://github.com/yeongseon/kpubdata/issues/80))
15
+ >
16
+ > **실API 최종 검증일**
17
+ >
18
+ > - 실API 검증을 마지막으로 성공한 날짜 (`YYYY-MM-DD`). 테스트 검증만 완료된 데이터셋은 `-`로 표기합니다.
19
+ > - 검증일이 90일을 초과한 데이터셋은 재검증을 권장합니다.
20
+
21
+ ## 현재 지원
22
+
23
+ | 상태 | 검증 | 실API 최종 검증일 | Provider | Dataset ID | 데이터셋명 | 인증 | 공식 문서 | 비고 |
24
+ |---|---|---|---|---|---|---|---|---|
25
+ | 지원 | 테스트 검증 | - | 공공데이터포털 (`datago`) | `apt_trade` | 아파트매매 실거래가 | [공공데이터포털](https://www.data.go.kr) 서비스키 | [data.go.kr](https://www.data.go.kr) | 국토교통부 제공 |
26
+ | 지원 | 테스트 검증 | - | 공공데이터포털 (`datago`) | `village_fcst` | 단기예보 조회서비스 | [공공데이터포털](https://www.data.go.kr) 서비스키 | [data.go.kr](https://www.data.go.kr) | 기상청 제공 |
27
+ | 지원 | 테스트 검증 | - | 공공데이터포털 (`datago`) | `ultra_srt_ncst` | 초단기실황 조회서비스 | [공공데이터포털](https://www.data.go.kr) 서비스키 | [data.go.kr](https://www.data.go.kr) | 기상청 제공 |
28
+ | 지원 | 테스트 검증 | - | 공공데이터포털 (`datago`) | `air_quality` | 대기오염정보 조회서비스 | [공공데이터포털](https://www.data.go.kr) 서비스키 | [data.go.kr](https://www.data.go.kr) | |
29
+ | 지원 | 테스트 검증 | - | 공공데이터포털 (`datago`) | `bus_arrival` | 경기도 버스도착정보 조회서비스 | [공공데이터포털](https://www.data.go.kr) 서비스키 | [data.go.kr](https://www.data.go.kr) | |
30
+ | 지원 | 테스트 검증 | - | 공공데이터포털 (`datago`) | `hospital_info` | 병원정보서비스 | [공공데이터포털](https://www.data.go.kr) 서비스키 | [data.go.kr](https://www.data.go.kr) | |
31
+ | 지원 | 실API 검증 | 2025-04-15 | 한국은행 ECOS (`bok`) | `base_rate` | 한국은행 기준금리 | [ECOS](https://ecos.bok.or.kr/api/) 인증키 | [ecos.bok.or.kr](https://ecos.bok.or.kr/api/) | |
32
+ | 지원 | 실API 검증 | 2025-04-15 | 통계청 KOSIS (`kosis`) | `population_migration` | 시도별 이동자수 | [KOSIS](https://kosis.kr/openapi/index/index.jsp) 인증키 | [kosis.kr](https://kosis.kr/openapi/index/index.jsp) | |
33
+ | 지원 | 실API 검증 | 2025-04-16 | 지방재정365 (`lofin`) | `expenditure_budget` | 세출결산총괄 | [지방재정365](https://www.lofin365.go.kr) 인증키 | [lofin365.go.kr](https://www.lofin365.go.kr) | API 코드: AJGCF |
34
+ | 지원 | 실API 검증 | 2025-04-16 | 지방재정365 (`lofin`) | `revenue_budget` | 세입결산총괄 | [지방재정365](https://www.lofin365.go.kr) 인증키 | [lofin365.go.kr](https://www.lofin365.go.kr) | API 코드: IIBBH |
35
+ | 지원 | 실API 검증 | 2025-04-16 | 지방재정365 (`lofin`) | `expenditure_function` | 기능별세출 | [지방재정365](https://www.lofin365.go.kr) 인증키 | [lofin365.go.kr](https://www.lofin365.go.kr) | API 코드: GGNSE |
36
+ | 지원 | 실API 검증 | 2025-04-16 | 지방재정365 (`lofin`) | `debt_ratio` | 채무비율현황 | [지방재정365](https://www.lofin365.go.kr) 인증키 | [lofin365.go.kr](https://www.lofin365.go.kr) | API 코드: HEDFC |
37
+ | 지원 | 실API 검증 | 2025-04-16 | 지방재정365 (`lofin`) | `fiscal_independence` | 재정자립도현황 | [지방재정365](https://www.lofin365.go.kr) 인증키 | [lofin365.go.kr](https://www.lofin365.go.kr) | API 코드: JFIED |
38
+ | 지원 | 실API 검증 | 2025-04-17 | 지방재정365 (`lofin`) | `revenue_by_source` | 재원별 회계별 세입결산 | [지방재정365](https://www.lofin365.go.kr) 인증키 | [lofin365.go.kr](https://www.lofin365.go.kr) | API 코드: FIACRV |
39
+
40
+ ## 진행 예정 / 진행 중
41
+
42
+ | 상태 | Provider | Dataset ID | 데이터셋명 | 메모 |
43
+ |---|---|---|---|---|
44
+ | 예정 | 공공데이터포털 (`datago`) | `apt_rent` | 아파트 전월세 실거래가 | `apt_trade`와 인접한 확장 후보 |
45
+ | 예정 | 기상청 | `weather_forecast` | 동네예보 | provider/adapter 구조 확정 후 착수 |
46
+
47
+ ## 갱신 규칙
48
+
49
+ 새로운 adapter를 추가할 때는 아래 절차에 따라 이 문서를 함께 업데이트합니다.
50
+
51
+ 1. **기획 단계**: `진행 예정 / 진행 중` 표에 `예정`으로 추가
52
+ 2. **구현 시작**: fixture 수집 또는 adapter skeleton이 생기면 `진행 중`으로 변경
53
+ 3. **지원 전환**: fixture + unit test + contract test + 문서가 모두 포함된 PR이 merge되면 `현재 지원` 표로 이동하고 `지원`으로 표시
54
+ 4. **README 동기화**: `지원` 항목만 [README.md](./README.md)의 요약 표에 추가
55
+ 5. **명칭 규칙**: provider slug와 dataset id는 코드의 실제 이름과 정확히 일치시킬 것
56
+ 6. **PR 포함**: adapter 추가/상태 변경 PR에는 이 문서의 업데이트를 반드시 포함
@@ -39,7 +39,6 @@ That means these should be standardized:
39
39
 
40
40
  - `client.dataset(id)`
41
41
  - `dataset.list(...)`
42
- - `dataset.get(...)`
43
42
  - `dataset.schema()`
44
43
  - `dataset.call_raw(...)`
45
44
 
@@ -218,7 +218,6 @@ classDiagram
218
218
  +search_datasets()
219
219
  +get_dataset()
220
220
  +query_records()
221
- +get_record()
222
221
  +get_schema()
223
222
  +call_raw()
224
223
  }
@@ -227,7 +226,6 @@ classDiagram
227
226
  +search_datasets()
228
227
  +get_dataset()
229
228
  +query_records()
230
- +get_record()
231
229
  +get_schema()
232
230
  +call_raw()
233
231
  }
@@ -244,7 +242,6 @@ classDiagram
244
242
  }
245
243
  class Dataset {
246
244
  +list()
247
- +get()
248
245
  +schema()
249
246
  +call_raw()
250
247
  }
@@ -418,9 +415,8 @@ graph TB
418
415
  M2["search_datasets(text) -> list[DatasetRef]"]
419
416
  M3["get_dataset(key) -> DatasetRef"]
420
417
  M4["query_records(dataset, query) -> RecordBatch"]
421
- M5["get_record(dataset, key) -> dict"]
422
- M6["get_schema(dataset) -> SchemaDescriptor"]
423
- M7["call_raw(dataset, operation, params) -> object"]
418
+ M5["get_schema(dataset) -> SchemaDescriptor"]
419
+ M6["call_raw(dataset, operation, params) -> object"]
424
420
  end
425
421
 
426
422
  Protocol --- Implementation
@@ -207,9 +207,9 @@ codes (most responses return HTTP 200 even on error).
207
207
  6. **`call_raw`** — pass through arbitrary params and return the
208
208
  **minimally processed** provider response dict. Do not flatten or
209
209
  canonicalize — this preserves the raw escape hatch.
210
- 7. **Unsupported capabilities** — `get_record` and `get_schema` are
211
- **not supported** by default unless dataset metadata explicitly
212
- proves otherwise. Do not mark these as supported.
210
+ 7. **Unsupported capabilities** — `get_schema` is **not supported** by
211
+ default unless dataset metadata explicitly proves otherwise. Do not
212
+ mark it as supported.
213
213
  8. **Item normalization** — always coerce `items.item` to `list[dict]`
214
214
  as described in §4.3.
215
215
 
@@ -146,6 +146,29 @@ kosis.population_migration — 시도별 이동자수 (Population Migration)
146
146
  - 공공데이터포털(datago): [datago API 키 발급 가이드](./providers/datago.md)
147
147
  - 통계청 KOSIS: [KOSIS API 키 발급 가이드](./providers/kosis.md)
148
148
 
149
+ ## Step 8: 전체 페이지 자동 조회
150
+
151
+ 데이터가 많을 때 `list_all()`을 사용하면 모든 페이지를 자동으로 순회합니다.
152
+
153
+ ```python
154
+ ds = client.dataset("bok.base_rate")
155
+
156
+ for batch in ds.list_all(start_date="202401", end_date="202412"):
157
+ print(f"{len(batch.items)}건 조회")
158
+ ```
159
+
160
+ ## Step 9: pandas로 데이터 분석하기
161
+
162
+ ```bash
163
+ pip install kpubdata[pandas]
164
+ ```
165
+
166
+ ```python
167
+ result = ds.list(start_date="202401", end_date="202412")
168
+ df = result.to_pandas()
169
+ print(df)
170
+ ```
171
+
149
172
  ## 다음 단계
150
173
  - 각 Provider별 상세 사용법: [docs/providers/](./providers/) 폴더
151
174
  - 프로젝트에 기여하기: [CONTRIBUTING.md](../CONTRIBUTING.md)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "kpubdata"
7
- version = "0.1.1"
7
+ version = "0.2.2"
8
8
  description = "Dialect-inspired Python access framework for Korean public data"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -2,6 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Generator
6
+ from typing import cast
7
+
8
+ from typing_extensions import override
9
+
5
10
  from kpubdata.core.capability import Operation
6
11
  from kpubdata.core.models import DatasetRef, Query, RecordBatch, SchemaDescriptor
7
12
  from kpubdata.core.protocol import ProviderAdapter
@@ -18,8 +23,8 @@ class Dataset:
18
23
  def __init__(self, ref: DatasetRef, adapter: ProviderAdapter) -> None:
19
24
  """Initialize a dataset bound to its canonical ref and adapter."""
20
25
 
21
- self._ref = ref
22
- self._adapter = adapter
26
+ self._ref: DatasetRef = ref
27
+ self._adapter: ProviderAdapter = adapter
23
28
 
24
29
  @property
25
30
  def ref(self) -> DatasetRef:
@@ -91,10 +96,18 @@ class Dataset:
91
96
  start_date = value
92
97
  elif key == "end_date" and isinstance(value, str):
93
98
  end_date = value
94
- elif key == "fields" and isinstance(value, list):
95
- fields_list = value
96
- elif key == "sort" and isinstance(value, list):
97
- sort_list = value
99
+ elif (
100
+ key == "fields"
101
+ and isinstance(value, list)
102
+ and all(isinstance(item, str) for item in cast(list[object], value))
103
+ ):
104
+ fields_list = cast(list[str], value)
105
+ elif (
106
+ key == "sort"
107
+ and isinstance(value, list)
108
+ and all(isinstance(item, str) for item in cast(list[object], value))
109
+ ):
110
+ sort_list = cast(list[str], value)
98
111
  else:
99
112
  filters[key] = value
100
113
 
@@ -110,24 +123,27 @@ class Dataset:
110
123
  )
111
124
  return self._adapter.query_records(self._ref, query)
112
125
 
113
- def get(self, **key: object) -> dict[str, object] | None:
114
- """Return a single record matching the provided key fields.
115
-
116
- Return ``None`` when no matching record is found.
117
-
118
- Raises:
119
- UnsupportedCapabilityError: If this dataset does not support ``get``.
120
- """
121
-
122
- if Operation.GET not in self._ref.operations:
126
+ def list_all(self, **kwargs: object) -> Generator[RecordBatch, None, None]:
127
+ if Operation.LIST not in self._ref.operations:
123
128
  raise UnsupportedCapabilityError(
124
- f"Dataset does not support get: {self._ref.id}",
129
+ f"Dataset does not support list: {self._ref.id}",
125
130
  provider=self._ref.provider,
126
131
  dataset_id=self._ref.id,
127
- operation=Operation.GET.value,
132
+ operation=Operation.LIST.value,
128
133
  )
129
- key_payload: dict[str, object] = {k: v for k, v in key.items()}
130
- return self._adapter.get_record(self._ref, key_payload)
134
+
135
+ page_kwargs = dict(kwargs)
136
+ batch = self.list(**page_kwargs)
137
+ yield batch
138
+ while batch.next_page is not None or batch.next_cursor is not None:
139
+ if batch.next_cursor is not None:
140
+ page_kwargs["cursor"] = batch.next_cursor
141
+ _ = page_kwargs.pop("page", None)
142
+ else:
143
+ page_kwargs["page"] = batch.next_page
144
+ _ = page_kwargs.pop("cursor", None)
145
+ batch = self.list(**page_kwargs)
146
+ yield batch
131
147
 
132
148
  def schema(self) -> SchemaDescriptor | None:
133
149
  """Return canonical schema metadata when the provider exposes it."""
@@ -144,6 +160,7 @@ class Dataset:
144
160
  payload: dict[str, object] = {k: v for k, v in params.items()}
145
161
  return self._adapter.call_raw(self._ref, operation, payload)
146
162
 
163
+ @override
147
164
  def __repr__(self) -> str:
148
165
  """Return concise debug representation."""
149
166
 
@@ -117,6 +117,23 @@ class RecordBatch:
117
117
  def __bool__(self) -> bool:
118
118
  return bool(self.items)
119
119
 
120
+ def to_pandas(self) -> object:
121
+ """Convert items to a pandas ``DataFrame``.
122
+
123
+ Requires the ``pandas`` optional dependency.
124
+ Install with: ``pip install kpubdata[pandas]``
125
+
126
+ Returns:
127
+ A ``pandas.DataFrame`` built from :attr:`items`.
128
+ """
129
+ try:
130
+ import pandas as pd # type: ignore[import-untyped]
131
+ except ImportError:
132
+ raise ImportError(
133
+ "pandas is required for to_pandas(). Install with: pip install kpubdata[pandas]"
134
+ ) from None
135
+ return pd.DataFrame(self.items)
136
+
120
137
 
121
138
  @_dataclass(slots=True)
122
139
  class FieldDescriptor: