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.
- kpubdata-0.2.2/.sisyphus/remove-get-record-plan.md +18 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/API_SPEC.md +14 -6
- {kpubdata-0.1.1 → kpubdata-0.2.2}/ARCHITECTURE.md +86 -1
- {kpubdata-0.1.1 → kpubdata-0.2.2}/CANONICAL_MODEL.md +0 -2
- {kpubdata-0.1.1 → kpubdata-0.2.2}/CHANGELOG.md +18 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/PKG-INFO +36 -1
- {kpubdata-0.1.1 → kpubdata-0.2.2}/PRD.md +2 -12
- {kpubdata-0.1.1 → kpubdata-0.2.2}/PROVIDER_ADAPTER_CONTRACT.md +9 -4
- {kpubdata-0.1.1 → kpubdata-0.2.2}/README.md +35 -0
- kpubdata-0.2.2/SUPPORTED_DATA.md +56 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/VALIDATION.md +0 -1
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/architecture-diagrams.md +2 -6
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/datago-api-reference.md +3 -3
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/quickstart.md +23 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/pyproject.toml +1 -1
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/dataset.py +37 -20
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/models.py +17 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/protocol.py +0 -5
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/bok/adapter.py +25 -38
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/datago/adapter.py +44 -51
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/kosis/adapter.py +1 -3
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/lofin/adapter.py +20 -32
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/registry.py +0 -1
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/test_bok.py +0 -5
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/test_datago.py +0 -5
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/test_kosis.py +0 -5
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/test_lofin.py +1 -6
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_client_flow.py +1 -22
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_exception_propagation.py +1 -41
- kpubdata-0.2.2/tests/unit/core/test_dataset.py +209 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_dataset_coverage.py +16 -3
- kpubdata-0.2.2/tests/unit/core/test_to_pandas.py +110 -0
- kpubdata-0.2.2/tests/unit/providers/bok/test_bok_adapter.py +86 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/test_adapter.py +35 -30
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/test_adapter_coverage.py +9 -18
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/test_fixtures.py +7 -4
- kpubdata-0.2.2/tests/unit/providers/lofin/test_lofin_adapter.py +85 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_catalog.py +4 -6
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_catalog_coverage.py +0 -3
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_client_coverage.py +0 -6
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_client_transport_requirements.py +0 -6
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_registry.py +6 -7
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_registry_coverage.py +0 -4
- {kpubdata-0.1.1 → kpubdata-0.2.2}/uv.lock +1 -1
- kpubdata-0.1.1/SUPPORTED_DATA.md +0 -51
- kpubdata-0.1.1/tests/unit/core/test_dataset.py +0 -139
- {kpubdata-0.1.1 → kpubdata-0.2.2}/.github/ISSUE_TEMPLATE/new-provider-adapter.yml +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/.github/workflows/ci.yml +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/.github/workflows/integration.yml +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/.github/workflows/publish-pypi.yml +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/.gitignore +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/AGENTS.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/CONTRIBUTING.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/LICENSE +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/PACKAGING.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/ROADMAP.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/adrs/0001-dialect-inspired-architecture.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/adrs/0002-standardize-ux-not-native-shape.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/product-family-architecture.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/providers/bok.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/providers/datago.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/providers/kosis.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/docs/tutorial-ai-agent-workflow.md +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/catalog.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/client.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/config.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/capability.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/core/representation.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/exceptions.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/_common.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/bok/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/bok/catalogue.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/datago/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/datago/catalogue.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/kosis/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/kosis/catalogue.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/lofin/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/providers/lofin/catalogue.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/py.typed +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/transport/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/transport/decode.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/transport/http.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/src/kpubdata/transport/retry.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/contract/provider_adapter.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/bok/error_auth.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/bok/success_empty.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/bok/success_single_page.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_auth_30.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_invalid_request_10.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_rate_limit_22.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_service_unavailable_01.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/error_xml_auth_30.xml +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_apt_trade.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_empty.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_multi_page_1.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_multi_page_2.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_single_item.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_single_page.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_string_numerics.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_xml.xml +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/datago/success_xml_single_item.xml +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/kosis/error_auth.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/kosis/success_empty.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/kosis/success_single_page.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/lofin/error_auth.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/lofin/success_fiacrv.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/fixtures/lofin/success_single_page.json +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/conftest.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_bok_live.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_datago_live.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_kosis_live.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_lofin_live.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/integration/test_transport_http.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_exceptions.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_exceptions_coverage.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_models.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/core/test_models_fixtures.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/providers/datago/conftest.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_catalogue_validation.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_config.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/test_config_coverage.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/__init__.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_decode.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_decode_coverage.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_http_coverage.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_http_coverage_extra.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_logging.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_retry.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_retry_after.py +0 -0
- {kpubdata-0.1.1 → kpubdata-0.2.2}/tests/unit/transport/test_retry_coverage.py +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
-
### `
|
|
94
|
+
### `list_all()`
|
|
86
95
|
|
|
87
|
-
Returns
|
|
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/
|
|
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.
|
|
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.
|
|
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.
|
|
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-
|
|
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.
|
|
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에는 이 문서의 업데이트를 반드시 포함
|
|
@@ -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["
|
|
422
|
-
M6["
|
|
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** — `
|
|
211
|
-
|
|
212
|
-
|
|
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)
|
|
@@ -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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
114
|
-
|
|
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
|
|
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.
|
|
132
|
+
operation=Operation.LIST.value,
|
|
128
133
|
)
|
|
129
|
-
|
|
130
|
-
|
|
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:
|