development-finance 0.1.0__py3-none-any.whl
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.
- development_finance-0.1.0.dist-info/METADATA +307 -0
- development_finance-0.1.0.dist-info/RECORD +84 -0
- development_finance-0.1.0.dist-info/WHEEL +4 -0
- development_finance-0.1.0.dist-info/entry_points.txt +3 -0
- dfo/__init__.py +65 -0
- dfo/_client/__init__.py +1 -0
- dfo/_client/analysis.py +234 -0
- dfo/_client/cards.py +94 -0
- dfo/_client/cli.py +160 -0
- dfo/_client/client.py +499 -0
- dfo/_client/default.py +348 -0
- dfo/_config/__init__.py +1 -0
- dfo/_config/credentials.py +145 -0
- dfo/_config/keys.py +344 -0
- dfo/_config/paths.py +119 -0
- dfo/_core/__init__.py +1 -0
- dfo/_core/codes.py +78 -0
- dfo/_core/errors.py +446 -0
- dfo/_core/json_text.py +66 -0
- dfo/_core/lookup_results.py +319 -0
- dfo/_core/nearest.py +45 -0
- dfo/_core/parse.py +509 -0
- dfo/_core/periods.py +79 -0
- dfo/_core/refs.py +787 -0
- dfo/_core/refusals.py +337 -0
- dfo/_core/text.py +323 -0
- dfo/_core/types.py +791 -0
- dfo/_datasets/__init__.py +1 -0
- dfo/_datasets/analysis.py +660 -0
- dfo/_datasets/build.py +753 -0
- dfo/_datasets/bundle.py +823 -0
- dfo/_datasets/cache.py +439 -0
- dfo/_datasets/cards.py +333 -0
- dfo/_datasets/dataset.py +1710 -0
- dfo/_datasets/fetch.py +763 -0
- dfo/_datasets/interrupt.py +31 -0
- dfo/_datasets/operation.py +567 -0
- dfo/_datasets/pin.py +109 -0
- dfo/_datasets/progress.py +160 -0
- dfo/_datasets/publication.py +667 -0
- dfo/_datasets/recipe.py +595 -0
- dfo/_datasets/resolve.py +1199 -0
- dfo/_datasets/shares.py +381 -0
- dfo/_generated/__init__.py +1 -0
- dfo/_generated/error_map.py +288 -0
- dfo/_generated/file_selector.py +10 -0
- dfo/_generated/reading_order.py +25 -0
- dfo/_generated/role_keys.py +18 -0
- dfo/_generated/strict.py +69 -0
- dfo/_generated/wire.py +3765 -0
- dfo/_http/__init__.py +1 -0
- dfo/_http/hooks.py +199 -0
- dfo/_http/transport.py +1442 -0
- dfo/_search/__init__.py +1 -0
- dfo/_search/answer.py +837 -0
- dfo/_search/answer_json.py +126 -0
- dfo/_search/display.py +256 -0
- dfo/_search/lookups.py +221 -0
- dfo/_search/search.py +46 -0
- dfo/_sync/__init__.py +1 -0
- dfo/_sync/_client/__init__.py +1 -0
- dfo/_sync/_client/client.py +498 -0
- dfo/_sync/_datasets/__init__.py +1 -0
- dfo/_sync/_datasets/analysis.py +655 -0
- dfo/_sync/_datasets/cards.py +334 -0
- dfo/_sync/_datasets/fetch.py +764 -0
- dfo/_sync/_datasets/operation.py +564 -0
- dfo/_sync/_datasets/pin.py +110 -0
- dfo/_sync/_datasets/resolve.py +1198 -0
- dfo/_sync/_datasets/shares.py +378 -0
- dfo/_sync/_http/__init__.py +1 -0
- dfo/_sync/_http/transport.py +1441 -0
- dfo/_sync/_search/__init__.py +1 -0
- dfo/_sync/_search/answer.py +838 -0
- dfo/_sync/_search/lookups.py +220 -0
- dfo/_sync/_search/search.py +47 -0
- dfo/_version.py +3 -0
- dfo/analysis.py +37 -0
- dfo/auth.py +19 -0
- dfo/cards.py +13 -0
- dfo/errors.py +65 -0
- dfo/lookup.py +18 -0
- dfo/py.typed +1 -0
- dfo/types.py +149 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: development-finance
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the Development Finance Observatory APIs
|
|
5
|
+
Keywords: development finance,ODA,aid,SDMX,Data Commons
|
|
6
|
+
Author: ONE Campaign
|
|
7
|
+
Author-email: ONE Campaign <data@one.org>
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: httpcore2>=2.15,<2.16 ; sys_platform != 'emscripten'
|
|
14
|
+
Requires-Dist: httpx2>=2.15,<2.16
|
|
15
|
+
Requires-Dist: pyarrow>=15
|
|
16
|
+
Requires-Dist: typing-extensions>=4.12
|
|
17
|
+
Requires-Dist: pandas>=2.2 ; extra == 'pandas'
|
|
18
|
+
Requires-Dist: polars>=1.0 ; extra == 'polars'
|
|
19
|
+
Requires-Dist: openpyxl>=3.1 ; extra == 'xlsx'
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Project-URL: Homepage, https://developmentfinance.org
|
|
22
|
+
Project-URL: Repository, https://github.com/DevelopmentFinanceObservatory/sdk
|
|
23
|
+
Project-URL: Issues, https://github.com/DevelopmentFinanceObservatory/sdk/issues
|
|
24
|
+
Project-URL: Changelog, https://github.com/DevelopmentFinanceObservatory/sdk/blob/main/python/CHANGELOG.md
|
|
25
|
+
Provides-Extra: pandas
|
|
26
|
+
Provides-Extra: polars
|
|
27
|
+
Provides-Extra: xlsx
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# development-finance
|
|
31
|
+
|
|
32
|
+
Ask the Development Finance Observatory (DFO) a question about aid, debt, or development indicators in your own words, and get a versioned table back.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv add "development-finance[pandas]" # in a uv project
|
|
36
|
+
pip install "development-finance[pandas]" # with pip
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import dfo
|
|
41
|
+
|
|
42
|
+
answer = dfo.search("ODA from Germany to Nigeria")
|
|
43
|
+
print(answer)
|
|
44
|
+
data = answer.fetch()
|
|
45
|
+
df = data.to_pandas()
|
|
46
|
+
|
|
47
|
+
growth = dfo.analysis.growth(data, window=(2015, 2024))
|
|
48
|
+
share = dfo.share(dfo.cards.line(data, title="German ODA to Nigeria"), latest=True)
|
|
49
|
+
print(share.url)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
Germany's gross disbursements to Nigeria for official development assistance, 1960 to 2024. Current US dollars · OECD DAC2a.
|
|
54
|
+
Fetch: answer.fetch()
|
|
55
|
+
https://developmentfinance.org/s/k3Qx8vTa
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`dfo.analysis.growth` computes the change from 2015 to 2024 on DFO, as a fraction where 0.25 is a 25% rise. `dfo.share` publishes a line chart of the data at a public URL that anyone with the link can open, and follows new data.
|
|
59
|
+
|
|
60
|
+
The package imports as `dfo` and needs Python 3.12 or later. It works without a key, under a per-IP quota. The table arrives as Arrow. The `pandas` extra adds `to_pandas()`, `polars` adds `to_polars()`, and `xlsx` lets `dfo.load` open an XLSX bundle from the website.
|
|
61
|
+
|
|
62
|
+
The code blocks on this page run in order, as cells of one notebook.
|
|
63
|
+
|
|
64
|
+
## Ask a question and read the answer
|
|
65
|
+
|
|
66
|
+
`dfo.search` returns an `Answer`, which says what DFO understood and holds no data yet. Printing it shows Search's sentence, naming the measure, places, years, unit, and source, and the line to type next. Read the sentence before you fetch. Search is model-driven, and a wrong reading gives a plausible table for the wrong question.
|
|
67
|
+
|
|
68
|
+
`answer.fetch()` builds the table on the server, downloads it, and checks the file against its digest. Printing the dataset shows what it is, how big, and how to keep it:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
<Dataset Germany's gross disbursements to Nigeria for official development assistance, 1960 to 2024. Current US dollars · OECD DAC2a.
|
|
72
|
+
Source: OECD DAC2A
|
|
73
|
+
Version: cv_1145ade0abb33920fefaf5daaeb8f3a9 (exact), held until 24 November 2026, not pinned
|
|
74
|
+
Rows: 65
|
|
75
|
+
Keep: data.save(path) · newest later: dfo.fetch(data.ref, latest=True)>
|
|
76
|
+
time_period financingProvider_label financingRecipient_label value
|
|
77
|
+
60 2020 Germany Nigeria 123608639.0
|
|
78
|
+
61 2021 Germany Nigeria 164254178.0
|
|
79
|
+
62 2022 Germany Nigeria 122707200.0
|
|
80
|
+
63 2023 Germany Nigeria 126863832.0
|
|
81
|
+
64 2024 Germany Nigeria 100707102.0
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import dfo
|
|
86
|
+
|
|
87
|
+
answer = dfo.search("ODA from Germany to Nigeria")
|
|
88
|
+
data = answer.fetch()
|
|
89
|
+
print(data)
|
|
90
|
+
|
|
91
|
+
df = data.to_pandas()
|
|
92
|
+
print(df.iloc[:, :4].tail())
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `_label` columns of names come first, covering the year, the parties, the value, and its unit. The other columns follow, codes included, with those that tell the rows apart before those that hold one value throughout, and `stat_var` last. The years of each series read in order. `len(data)` is the row count, `data.to_polars()` gives a polars frame, and `pyarrow.table(data)` gives an Arrow table. While a build runs on the server, `fetch` shows its progress in a terminal or notebook. [Long builds](docs/long-builds.md) covers waiting, timeouts, and Ctrl-C.
|
|
96
|
+
|
|
97
|
+
## Choose a reading
|
|
98
|
+
|
|
99
|
+
Some questions have more than one reading. "government education spending Kenya" can mean a share of the budget or a share of GDP, and the answer lists both with the line that settles it:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
Answered 0 of 1 part of your question.
|
|
103
|
+
missing "government education spending": 2 readings
|
|
104
|
+
[0] Kenya's percent of total government expenditure on education, 1980 to 2025 · World Development Indicators
|
|
105
|
+
[1] Kenya's percent of nominal GDP spent on education, 1971 to 2024 · World Development Indicators
|
|
106
|
+
Choose: answer.choose(0).fetch()
|
|
107
|
+
Kenya's percent of total government expenditure on education, 1980 to 2025. World Development Indicators.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
answer = dfo.search("government education spending Kenya")
|
|
112
|
+
print(answer)
|
|
113
|
+
|
|
114
|
+
data = answer.choose(0).fetch()
|
|
115
|
+
print(data.sentence)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`choose` takes the option's index or its full label, and makes one Search request. The new answer and its sentence are Search's. The order of readings can change between searches, so a script that runs again later keeps `data.ref` and fetches that. A place that matches several countries, such as "Congo", is settled the same way, with `answer.choose("Congo [DRC]")`. [Answers and choices](docs/answers.md) covers questions with several parts and answers that leave a part out.
|
|
119
|
+
|
|
120
|
+
## Fetch other years or places
|
|
121
|
+
|
|
122
|
+
The simplest way is to put the years and places in the question:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
Germany's gross disbursements to Ghana and Kenya for official development assistance, 2015 to 2022. Current US dollars · OECD DAC2a.
|
|
126
|
+
Fetch: answer.fetch()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
answer = dfo.search("ODA from Germany to Kenya and Ghana, 2015 to 2022")
|
|
131
|
+
print(answer)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
To vary a reading you already have without a new search, pass its spec to `dfo.fetch` with `window` and `entities`. `window` takes `(start_year, end_year)` with years from 1000 to 2200, `"latest"`, or `"all"`. `entities` maps each role the spec names to place codes, keyed `place` for the place a measure is about and `origin` and `destination` for a flow, and `spec.roles` lists those roles with the places each binds, or None for a role left open. `answer.fetch` takes the same arguments and narrows every reading in the answer:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
<Dataset Germany's gross disbursements to Ghana and Kenya for official development assistance, 2015 to 2022. Current US dollars · OECD DAC2a.
|
|
138
|
+
Source: OECD DAC2A
|
|
139
|
+
Version: cv_6cdec57990a5712642ad97ab58d9ea21 (exact), held until 24 November 2026, not pinned
|
|
140
|
+
Rows: 16
|
|
141
|
+
Keep: data.save(path) · newest later: dfo.fetch(data.ref, latest=True)>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
spec = dfo.search("ODA from Germany to Nigeria").specs[0]
|
|
146
|
+
print(spec.roles)
|
|
147
|
+
# {'destination': ('country/NGA',), 'origin': ('country/DEU',)}
|
|
148
|
+
|
|
149
|
+
data = dfo.fetch(
|
|
150
|
+
spec,
|
|
151
|
+
window=(2015, 2022),
|
|
152
|
+
entities={"destination": ["country/KEN", "country/GHA"]},
|
|
153
|
+
)
|
|
154
|
+
print(data)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
[Look up a place code](#look-up-a-place-code) finds the code for a name. [Datasets and versions](docs/datasets.md) covers `facets` and the frame layout.
|
|
158
|
+
|
|
159
|
+
## Keep group and total rows
|
|
160
|
+
|
|
161
|
+
A table of flows leaves out the rows for groups and totals, such as "DAC countries" or "official donors (DAC)", so a sum over it counts each donor once. Pass `party_totals=True` to keep them. For ODA to Ethiopia in 2022, the default table has 95 rows, one per donor, and the full one has 110:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
answer = dfo.search("ODA to Ethiopia in 2022 by donor")
|
|
165
|
+
by_donor = answer.fetch()
|
|
166
|
+
with_totals = answer.fetch(party_totals=True)
|
|
167
|
+
|
|
168
|
+
print(len(by_donor), len(with_totals)) # 95 110
|
|
169
|
+
print(with_totals.to_pandas()["financingProvider_kind"].value_counts())
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
financingProvider_kind
|
|
174
|
+
official 72
|
|
175
|
+
private 23
|
|
176
|
+
total 13
|
|
177
|
+
whole total 2
|
|
178
|
+
Name: count, dtype: int64
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`financingProvider_kind` tells the rows apart: `official` and `private` for one donor, `total` for a group, and `whole total` for the source's figure across every donor. Both fetches issue a `SearchWarning` that no donor was named, which is the reading wanted here. `dfo.fetch(spec, party_totals=True)` does the same for a spec.
|
|
182
|
+
|
|
183
|
+
## Save the data and load it later
|
|
184
|
+
|
|
185
|
+
`data.save(path)` writes one Parquet file that carries the table, the citation, and the ref. `dfo.load(path)` reads it back offline:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
data = dfo.search("Kenya's external debt service").fetch()
|
|
189
|
+
data.save("kenya-debt.parquet")
|
|
190
|
+
|
|
191
|
+
saved = dfo.load("kenya-debt.parquet")
|
|
192
|
+
print(saved.ref == data.ref, len(saved)) # True 55
|
|
193
|
+
print(saved.citation.text)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The file is the record of what you analysed. The Parquet file reads in any Arrow tool, and its provenance sits in the file's metadata under `dfo.*` keys.
|
|
197
|
+
|
|
198
|
+
## Refresh later and tell if the data changed
|
|
199
|
+
|
|
200
|
+
`data.ref` names the exact version a dataset holds. `dfo.fetch(ref)` returns those rows again, and `dfo.fetch(ref, latest=True)` builds the newest version of the same reading. Two refs are equal when they name the same versions:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
old = dfo.load("kenya-debt.parquet")
|
|
204
|
+
new = dfo.fetch(old.ref, latest=True)
|
|
205
|
+
|
|
206
|
+
if new.ref != old.ref:
|
|
207
|
+
print("DFO's data has changed since you saved it")
|
|
208
|
+
new.save("kenya-debt.parquet")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
A version marked "held until" in the dataset's display can be fetched by its ref until that date, and one marked "pinned" is kept without a limit. The saved file keeps its rows either way. [Datasets and versions](docs/datasets.md) covers refs and retention.
|
|
212
|
+
|
|
213
|
+
## Share an answer
|
|
214
|
+
|
|
215
|
+
`answer.to_json()` writes the answer as JSON, and `dfo.Answer.from_json(text)` reads it back, ready to choose and fetch. A web app uses this to show the readings on one request and build the reader's choice on the next:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
answer = dfo.search("government education spending Kenya")
|
|
219
|
+
text = answer.to_json()
|
|
220
|
+
|
|
221
|
+
restored = dfo.Answer.from_json(text)
|
|
222
|
+
data = restored.choose(1).fetch()
|
|
223
|
+
print(data.sentence)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Answer JSON that comes back from a browser can carry any reading, and its build runs on your key. [Answers and choices](docs/answers.md) says how to keep a web app safe with it.
|
|
227
|
+
|
|
228
|
+
## Look up a place code
|
|
229
|
+
|
|
230
|
+
`dfo.lookup.resolve` turns names into the codes `entities` takes. It returns one `Match` per name, which prints what the name resolved to. A name that matches several places lists them, and a misspelt name names the nearest:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
for match in dfo.lookup.resolve("Nigeria", "Congo", "Germny"):
|
|
234
|
+
print(match)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
Nigeria -> Nigeria country/NGA (country)
|
|
239
|
+
Congo -> ambiguous: Congo [DRC] country/COD | Congo [Republic] country/COG
|
|
240
|
+
Germny -> not found; nearest: Germany country/DEU
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`match.entity.dcid` holds the code of a resolved name, `match.candidates` the places an ambiguous one could mean, and `match.near_matches` the nearest spellings. `match.raw` keeps the server's result. `resolve` also takes `kinds`, such as `["country"]`, to limit the node kinds a name may match, `code_system` with `kinds=["code"]` to match one code system's codes, such as `CRSPurposeCodeEnum`, and `expand_members=True` to inline each group's members. `dfo.lookup.node("country/NGA")` returns one place, with the regions it belongs to in `node.raw`, and `as_of=` reads the graph on a past date. `dfo.lookup.code("spec_not_found")` explains a code from an error, and `dfo.lookup.catalogue()` lists what Search holds data on, with example questions. The result types live in `dfo.types`.
|
|
244
|
+
|
|
245
|
+
## Settings, keys, and errors
|
|
246
|
+
|
|
247
|
+
`dfo.search` and `dfo.fetch` use a default client that reads `DFO_BASE_URL` and your stored key. `dfo.Client` carries the same calls with its own settings, and closes its connections on exit:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
with dfo.Client(timeout=60.0, max_retries=3) as client:
|
|
251
|
+
data = client.search("Kenya's external debt service").fetch()
|
|
252
|
+
print(len(data))
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`Client` takes `api_key`, `base_url`, `timeout` (one request attempt, 30 seconds by default), `max_retries` (0 to 10, default 2), `cache_dir`, `cache`, `headers`, and `header_provider`. `dfo.configure(...)` takes the same options and replaces the default client. `dfo.AsyncClient` has the same methods as coroutines. `client.lookup` holds the lookups. `client.with_options(timeout=..., max_retries=...)` returns a copy with other limits that shares the client's connections and cache.
|
|
256
|
+
|
|
257
|
+
Run `dfo auth login` to store a key, and `dfo auth status` to see which key the client sends. [Keys and credentials](docs/keys.md) covers the lookup order.
|
|
258
|
+
|
|
259
|
+
Every error derives from `dfo.errors.DfoError`, carries a `code`, and ends its message with `[code]`, or `[code · trace id]` when a response carried one. A refusal names the fix, such as the call that settles an incomplete answer. [Errors](docs/errors.md) lists every class by what you do next. The types answers and datasets contain live in `dfo.types`.
|
|
260
|
+
|
|
261
|
+
## Limits the client enforces
|
|
262
|
+
|
|
263
|
+
The Python and TypeScript clients both apply these limits. The constant names are those in the Python source.
|
|
264
|
+
|
|
265
|
+
| Rule | Value |
|
|
266
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
267
|
+
| `Retry-After` parse | ASCII digits (seconds) or an IMF-fixdate such as `Sun, 06 Nov 1994 08:49:37 GMT`. Any other value is ignored, and the client's own backoff applies. A date in the past means 0 |
|
|
268
|
+
| `Retry-After` wait | The client waits when the value is at most 60 s (`RETRY_AFTER_MAX_S`) and fits the time left before the call's deadline. Otherwise it raises the refusal's typed error at once with `retry_after` set |
|
|
269
|
+
| Poll interval | The server's value, or a refused poll's `Retry-After`, clamped to 1 s (`POLL_INTERVAL_MIN_S`) to 60 s (`POLL_INTERVAL_MAX_S`), then to the time left before the deadline, where the client polls once more |
|
|
270
|
+
| Versions walk | Pages of 100 versions, at most 20 pages (`VERSIONS_PAGE_BUDGET`). A page cursor seen before raises `contract_violation`. Over the budget, no newest version is reported |
|
|
271
|
+
| Body size | Decoded bytes are counted while the body streams, and the read stops at the limit with `response_too_large`. A file's limit is its manifest's `bytes`, else 2 GiB (`DOWNLOAD_MAX_BYTES`). A JSON body's limit is 16 MiB (`JSON_BODY_MAX_BYTES`) |
|
|
272
|
+
| Keys | Visible ASCII only, 0x21 to 0x7E. The key-format check (prefix, base62, checksum) only warns |
|
|
273
|
+
| Header values | Visible ASCII, space, and tab. A control character, DEL, or a non-ASCII character raises `header_invalid` |
|
|
274
|
+
| `timeout` | A finite number above 0. A call's own `timeout` bounds the whole call (60 seconds for `search`, and no limit for `fetch` unless you pass one) |
|
|
275
|
+
| `max_retries` | An integer from 0 to 10. A bool is refused |
|
|
276
|
+
| Error messages | Name the source and the position of the offending character, and leave the key or header value out |
|
|
277
|
+
|
|
278
|
+
A key travels only to an https base URL, or to a loopback one over http: `localhost`, a name under `.localhost`, an address in 127.0.0.0/8 written as four decimal numbers, or `[::1]`. Any other plain-http base URL with a key raises `insecure_transport`. In synchronous code, platform name resolution can run past a deadline, and the asynchronous client can cancel it.
|
|
279
|
+
|
|
280
|
+
A process forked from a threaded parent keeps the default client and the proxy settings the parent resolved. A client the child builds for itself reads the platform's proxy settings in the child, which can crash a macOS child forked from a process with running threads. Build the client in the parent, or start workers with `spawn`.
|
|
281
|
+
|
|
282
|
+
The TypeScript client follows the same contract. It spells "no limit" as `Infinity` where Python uses `None`, decodes a Parquet file's row values on first use where Python decodes them when `fetch` returns, and writes refs without spaces after separators. Each client reads the other's refs.
|
|
283
|
+
|
|
284
|
+
## Documentation
|
|
285
|
+
|
|
286
|
+
- [Answers and choices](docs/answers.md): parts, readings, `choose`, partial fetches, shared answers, and declined questions.
|
|
287
|
+
- [Datasets and versions](docs/datasets.md): narrowing a fetch, layouts, conversions, refs, retention, `save`, and `load`.
|
|
288
|
+
- [Long builds](docs/long-builds.md): progress, deadlines, and what Ctrl-C leaves running.
|
|
289
|
+
- [Compute a figure on DFO](docs/compute.md): `dfo.analysis`, recipes, `latest_year`, recomputing on new data, and the series an operation leaves out.
|
|
290
|
+
- [Cards and shares](docs/shares.md): charts, tables and headline numbers, public share links, write tokens, snapshots, and what a share showed on a date.
|
|
291
|
+
- [Errors](docs/errors.md): every error class and code, by what you do next.
|
|
292
|
+
- [Keys and credentials](docs/keys.md): the key lookup order, `dfo auth login`, and the credentials file.
|
|
293
|
+
- [The cache](docs/cache.md): where downloads go, `DFO_OFFLINE`, and stale reads.
|
|
294
|
+
|
|
295
|
+
[llms.txt](llms.txt) summarises the package for language models.
|
|
296
|
+
|
|
297
|
+
## Development
|
|
298
|
+
|
|
299
|
+
The repository root holds the contract, the pre-commit configuration, and the CI workflows, and [`CONTRIBUTING.md`](../CONTRIBUTING.md) there explains them. From this directory:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
uv sync
|
|
303
|
+
uv run pytest
|
|
304
|
+
uv run ruff check
|
|
305
|
+
uv run ruff format --check
|
|
306
|
+
uv run ty check
|
|
307
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
dfo/__init__.py,sha256=xiTGkieJVNyF_AZm9egUeZ1Zb0HKYBsk-1DUOzDtqac,1915
|
|
2
|
+
dfo/_client/__init__.py,sha256=4y6ze-KTIo5CELNAbEY6hfxtqd3ykwJipP1CWwwLCCg,82
|
|
3
|
+
dfo/_client/analysis.py,sha256=OtrKv8lgIwvkJm4HkG7pp1PvMi1-TjqC2_vTRB6myoo,7322
|
|
4
|
+
dfo/_client/cards.py,sha256=hEdsR_yj6-7ku1VnHVLwGtOkZL5Y4jbldtMDgXXmrBQ,3139
|
|
5
|
+
dfo/_client/cli.py,sha256=1RTp8Tna8Y2jNK49p0peKVsK3hoyO9ovtoZ0Si3at_w,5544
|
|
6
|
+
dfo/_client/client.py,sha256=6kArseMwcjdrwdgrM8QnGzuA1e1dxuKRoFC8oW4Jl1I,23161
|
|
7
|
+
dfo/_client/default.py,sha256=OLemOoXOaJ02Cf5e7G4nZoyF2hknCRpjQqDBi5zmxU0,13270
|
|
8
|
+
dfo/_config/__init__.py,sha256=MZT7M0MAS6uXMoU8MWvJsEnnXiDjfZls_z3LjwoTlbk,87
|
|
9
|
+
dfo/_config/credentials.py,sha256=2aswKYtutA0POIw45rA59YUYNWZTNDOlg_Gxz0bLS8w,5156
|
|
10
|
+
dfo/_config/keys.py,sha256=XiVqmShZ5FcKoRySJP8ed9KA9HSKSKB7DxiAZSXdVuQ,11727
|
|
11
|
+
dfo/_config/paths.py,sha256=pPqVoA1OIEwVhlSf05XS0D-Fk_0F9ZdzeIuxuYRf6iE,4269
|
|
12
|
+
dfo/_core/__init__.py,sha256=bI2o2-lQxD8K76pvXmE9-ioeV-qoqT_YoLMYcNDsyDc,75
|
|
13
|
+
dfo/_core/codes.py,sha256=a2dRFXvFOf49sIJBDNniZJcdPqi7gAJS3w9oEQKXd28,3454
|
|
14
|
+
dfo/_core/errors.py,sha256=-HPCm4nDW5BdqJXP64wwY7iuqx56SdDG63L_xWhUlTw,15195
|
|
15
|
+
dfo/_core/json_text.py,sha256=MVr5lxzVcOyV5r4w0m0j9lFbq7yIlkZYkSRe5nuUYAU,3075
|
|
16
|
+
dfo/_core/lookup_results.py,sha256=R5AuMgqrxwJ9qPqTsGLq1gktqrEQFuFfNrd5zqcIosQ,10413
|
|
17
|
+
dfo/_core/nearest.py,sha256=HgtlaYkz6lyukLYQFURjhCSufO5XNR7Vk1hJ39KHQdQ,1728
|
|
18
|
+
dfo/_core/parse.py,sha256=SMFNGW6X57VHbhrBW773x8J6YBFgASLtCQDjoyIxA_s,17874
|
|
19
|
+
dfo/_core/periods.py,sha256=6HE5m3TTGIES-OUkK1pkeNWItmpO2GWsytnmNxbdxBw,2748
|
|
20
|
+
dfo/_core/refs.py,sha256=DlPoJvAfCLHAqEsC4Yb1gqK_AqFozZRB5tARtknAm10,28612
|
|
21
|
+
dfo/_core/refusals.py,sha256=Cm0Tgyd9WSnhh3BwFfssgnLn9kKiDQKNxRR1s2BxxZk,13768
|
|
22
|
+
dfo/_core/text.py,sha256=567hFrYqTOsbL8PK5anq4zElJ4m2kQmaoAp4i256uSg,12514
|
|
23
|
+
dfo/_core/types.py,sha256=HS7aruh3xk35WKDCKC0LJLL2TpO4ecMxKIeHcn-JaPs,27309
|
|
24
|
+
dfo/_datasets/__init__.py,sha256=Gv7B_IkXZ5DgjmcVw7hdkpVmpjAUCB6H1RFvhk-u5IU,80
|
|
25
|
+
dfo/_datasets/analysis.py,sha256=8vTatXYfza2GxxzBU8elGCdUjJ7htgZS7rZHXCte9E8,24058
|
|
26
|
+
dfo/_datasets/build.py,sha256=AHE2oeKpfSRk4GRDWGeJTMcQk27s8Vsx5EuJBNYckWQ,26911
|
|
27
|
+
dfo/_datasets/bundle.py,sha256=THfmSguP38mpsXAcOovFxX4q1RYULSDxmsxUEEvlYas,30220
|
|
28
|
+
dfo/_datasets/cache.py,sha256=zfCgks266PAvoksUTwTrXQhbKfhPkgTEOqeU_Qol-Qg,16706
|
|
29
|
+
dfo/_datasets/cards.py,sha256=JuyjqX8Xu-duTQgEeJ6wkM9S0dwpLDv-aNJNVAKWgZA,12824
|
|
30
|
+
dfo/_datasets/dataset.py,sha256=7mbIiHgmhWkGDMAfvZHTjctHwAO0C2GmiS7OXB7d1UM,66673
|
|
31
|
+
dfo/_datasets/fetch.py,sha256=C2b2bwDvsz0QrBUsOYN2IHrtmG-9MCG_o8XJA58o0yk,28575
|
|
32
|
+
dfo/_datasets/interrupt.py,sha256=FRsmAQw0sLNyoB5o1UFIVhGzcrK03ScDQkyvVrYK7S4,1063
|
|
33
|
+
dfo/_datasets/operation.py,sha256=f_zACl0Ylp5YSdhHTkyv7hLsarT3IyTdtFWuAxNviSQ,21432
|
|
34
|
+
dfo/_datasets/pin.py,sha256=PSnbGRhvjkrporE0KR5vsrmQHHYxsvNzPDynuLgiOYU,4701
|
|
35
|
+
dfo/_datasets/progress.py,sha256=_FJgsKCABDw_moTOE2l173j5mcdCgJuTDa5k1sGeEBA,5425
|
|
36
|
+
dfo/_datasets/publication.py,sha256=WQdSgGVegcK6XypP4VXhQwz_9CHKVadXFTS0JV55evU,24990
|
|
37
|
+
dfo/_datasets/recipe.py,sha256=sff4wpNH93Do8_KXmFIE9pWafWPjb14arM7GpX9_mFw,23400
|
|
38
|
+
dfo/_datasets/resolve.py,sha256=W213sa1V0e5WoAO1FnoI3SQeyu03ztTH6hNXOBsc7rY,44133
|
|
39
|
+
dfo/_datasets/shares.py,sha256=327QiFsRj6sChfXzW2fAaeB3qHW3uCE8WM3eXQNvDHg,15381
|
|
40
|
+
dfo/_generated/__init__.py,sha256=1WvrJoFlVaKv7LeNOws77l67IMjqz4oQGpQEBJqzprM,68
|
|
41
|
+
dfo/_generated/error_map.py,sha256=dpLIbBTs4Qi3uNBwPj7Kck6VIhqfbFCTA_-nma59Lck,11173
|
|
42
|
+
dfo/_generated/file_selector.py,sha256=CPjd5vXMgvVQNf4adoF1wsqaHANKHYzAqsNJNlDCYl8,329
|
|
43
|
+
dfo/_generated/reading_order.py,sha256=pfOb7heS2bKf8dSQsEOp1OQ3C8vKki5XViqhR0WNVRM,787
|
|
44
|
+
dfo/_generated/role_keys.py,sha256=WzszI43bqiBlOefISxHIfk8kOqxmwjvIRGcXi-ABnIw,571
|
|
45
|
+
dfo/_generated/strict.py,sha256=mIvUEBkVfKW4tfKpVwfzCSq-z3SMIWRnT-_kMKza2dg,2526
|
|
46
|
+
dfo/_generated/wire.py,sha256=sI_JIJItrBffRXB1YTC7_MQmajVdCRC__NApJqxH1ic,90007
|
|
47
|
+
dfo/_http/__init__.py,sha256=ChYPgAGF1FWhS_EpCwacdS7mphtJ3QBTljl1lKEXbwM,89
|
|
48
|
+
dfo/_http/hooks.py,sha256=Y2geHeKJpwmLp1YtQpobLGBFjzSw1x2JjvFjuU3adlo,6848
|
|
49
|
+
dfo/_http/transport.py,sha256=oT8sXZg8ID7q2n75NsBKBHnFI-u2gMyQhEWBBqQR-_o,58071
|
|
50
|
+
dfo/_search/__init__.py,sha256=lC9-yBM-DMK9uC4CSiawK6ZODe7O599QGJs5hFZlnmo,70
|
|
51
|
+
dfo/_search/answer.py,sha256=AfpLdlw0YaWaOSGD_B3udSZ2DRGCmj5bOEhgwBMf-ek,33162
|
|
52
|
+
dfo/_search/answer_json.py,sha256=YbQvF7U9wHZgq-VS4zs9lnv5KBd_AHcn1NJO0YRdhUY,4585
|
|
53
|
+
dfo/_search/display.py,sha256=60zB4Rd6dLa4ux5iJWOB8WA3zr_1Xt_bu_raKJs0RDc,8944
|
|
54
|
+
dfo/_search/lookups.py,sha256=0RfT9SI5gwJqRq7rx21OEjA5U2iIQEQ-Z8EnKrD7SXE,8027
|
|
55
|
+
dfo/_search/search.py,sha256=c5yoch1iISjfKHEeRY5usVUEVR-epJG9Yzof-jLfNRg,1862
|
|
56
|
+
dfo/_sync/__init__.py,sha256=ckMI6SeT8hJVJayuMAS16GpGnus_ymJOVpNpj_WNpzY,81
|
|
57
|
+
dfo/_sync/_client/__init__.py,sha256=hey-bmgnNvgkTL4PazPsBT_PDFksWnUcZDwHuvhB8-w,72
|
|
58
|
+
dfo/_sync/_client/client.py,sha256=fspew20svIw3uynx3xDzl8SopkkqAcPGpIVUwH0UA3I,23101
|
|
59
|
+
dfo/_sync/_datasets/__init__.py,sha256=fMrijgwTWguIL0wM7rLk3m87kricCdiKjvIRSNcvtC4,74
|
|
60
|
+
dfo/_sync/_datasets/analysis.py,sha256=9iQqolfgjcnb1mohA1xQ5ZStNXT42OlfWLPyic-q0wk,23808
|
|
61
|
+
dfo/_sync/_datasets/cards.py,sha256=is1WvdweQHsY-yHSLlbuU3wRt6O2NkqhC8kF_YcK1kw,12798
|
|
62
|
+
dfo/_sync/_datasets/fetch.py,sha256=3heitShZ8RntthvW0DSJLmF2XuQLLNFPrbPQszEpA88,28440
|
|
63
|
+
dfo/_sync/_datasets/operation.py,sha256=WRG0SvWHIzhmZw2PlcxtZJCoyzVTMztv6EYp-MGS2o8,21374
|
|
64
|
+
dfo/_sync/_datasets/pin.py,sha256=nyvPHNPZ1HaRGEvdEBTTxJQWEvjJMvB3roWAqHS7b8I,4754
|
|
65
|
+
dfo/_sync/_datasets/resolve.py,sha256=UdQKu7AVygsO7jjHb3eocbn5KXYDhrBYKDyzA5Eu0XA,43861
|
|
66
|
+
dfo/_sync/_datasets/shares.py,sha256=Ywl80IyOUT8L85zm_Ox42zvlrxi_amOvABzxstdGlA8,15265
|
|
67
|
+
dfo/_sync/_http/__init__.py,sha256=bqIjGsfzAsTBwMCPwrEyMJyfEaV8ckspOFcbw56adkg,70
|
|
68
|
+
dfo/_sync/_http/transport.py,sha256=WBNPLu-GOYHScRM4qWei4pGyoKZlo6Ga8lYMTvxkP30,57771
|
|
69
|
+
dfo/_sync/_search/__init__.py,sha256=sTw8vVT6vhf9-wuo1Ue84cd62QUbAVaTCv-IQffomZo,72
|
|
70
|
+
dfo/_sync/_search/answer.py,sha256=j5CIgW06OhgmRRtC6AJp-LlGeODLLDpTFoqI4sDM3jU,33183
|
|
71
|
+
dfo/_sync/_search/lookups.py,sha256=typOtO27xoOwQbrIyWWkxYcI2_9jaS545oW2Pv7TtRU,8031
|
|
72
|
+
dfo/_sync/_search/search.py,sha256=sTa9Gu3fSe0S5fhyO-CGxDYeEe4rUzb2i9fFXY_1e3A,1939
|
|
73
|
+
dfo/_version.py,sha256=74uw1papg0T7vMpgmplAbDa-OUEP4roFGMsYDkd7bdQ,89
|
|
74
|
+
dfo/analysis.py,sha256=Vvm31BC5LEfgpKX8oqp6iP0lQ0ThQTVSioLZXH6W1_E,1070
|
|
75
|
+
dfo/auth.py,sha256=Z--rO7hCZlguLZ4JWFHPb4l7gpHkNKq4sbiYqWTK1iU,595
|
|
76
|
+
dfo/cards.py,sha256=DwFFDIgABUrPs-pen3mKIWtZ7tGIWiBKLkwJiqKZXiM,626
|
|
77
|
+
dfo/errors.py,sha256=8gId2Tqg46omOeG1FjMiMfgKl2e_050zlOdwNpSekLs,1848
|
|
78
|
+
dfo/lookup.py,sha256=iWwDk5vNt9gskMfPNtA_9FUzp-0et5aNsgvmRCF6do0,564
|
|
79
|
+
dfo/py.typed,sha256=2fYELBifmrW34-PPJa0CTNoeda-08ZmdmE8dw_OKb_U,71
|
|
80
|
+
dfo/types.py,sha256=VmO8FNWp14raqnzT6OBRHMT8jcJMMOwLsQ309hkzqV8,3348
|
|
81
|
+
development_finance-0.1.0.dist-info/WHEEL,sha256=3OmCxFA0ffBIsX4x0o8CdfSvZAOvO3mzEG8U_z5TvV4,80
|
|
82
|
+
development_finance-0.1.0.dist-info/entry_points.txt,sha256=OTrrD_ZrrLW5Cs7LRKjpsbwgAZMdiXQrjpzn8y_i1Rg,46
|
|
83
|
+
development_finance-0.1.0.dist-info/METADATA,sha256=Rlkuwc8j7XR7JeWiAbLQ2BxigrOr0wwQi2AxR1bK2yI,18989
|
|
84
|
+
development_finance-0.1.0.dist-info/RECORD,,
|
dfo/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Python client for the Development Finance Observatory APIs.
|
|
2
|
+
|
|
3
|
+
`dfo.search(question)` returns an `Answer` that says what DFO understood.
|
|
4
|
+
`answer.fetch()` or `dfo.fetch(spec_or_ref)` returns a `Dataset`, which
|
|
5
|
+
`data.save` writes to a Parquet file and `dfo.load` reads back. `Client` and
|
|
6
|
+
`AsyncClient` carry the same calls for one configuration each.
|
|
7
|
+
|
|
8
|
+
`dfo.pin(data)` keeps a Dataset's versions fetchable with no time limit, and
|
|
9
|
+
`dfo.latest_year(offset)` names a year DFO settles each time an operation runs.
|
|
10
|
+
`dfo.share(cards, latest=...)` publishes cards from `dfo.cards` as a link that
|
|
11
|
+
follows new data or stays fixed; `dfo.snapshot`, `dfo.unshare` and
|
|
12
|
+
`dfo.history` fix it, take it down and list what it served.
|
|
13
|
+
|
|
14
|
+
The task modules hold the rest: `dfo.analysis` DFO's operations and recipes,
|
|
15
|
+
`dfo.cards` the charts, tables and numbers DFO draws,
|
|
16
|
+
`dfo.errors` the errors and warnings, `dfo.types` what answers and datasets
|
|
17
|
+
contain, `dfo.lookup` names, nodes and codes, and `dfo.auth` keys and headers.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from dfo import analysis, auth, cards, errors, lookup, types
|
|
21
|
+
from dfo._client.client import AsyncClient
|
|
22
|
+
from dfo._client.default import (
|
|
23
|
+
configure,
|
|
24
|
+
fetch,
|
|
25
|
+
history,
|
|
26
|
+
pin,
|
|
27
|
+
search,
|
|
28
|
+
share,
|
|
29
|
+
snapshot,
|
|
30
|
+
unshare,
|
|
31
|
+
)
|
|
32
|
+
from dfo._core.periods import latest_year
|
|
33
|
+
from dfo._core.refs import DatasetRef
|
|
34
|
+
from dfo._core.types import Spec
|
|
35
|
+
from dfo._datasets.bundle import load
|
|
36
|
+
from dfo._datasets.dataset import Dataset
|
|
37
|
+
from dfo._sync._client.client import Client
|
|
38
|
+
from dfo._sync._search.answer import Answer
|
|
39
|
+
from dfo._version import __version__
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"Answer",
|
|
43
|
+
"AsyncClient",
|
|
44
|
+
"Client",
|
|
45
|
+
"Dataset",
|
|
46
|
+
"DatasetRef",
|
|
47
|
+
"Spec",
|
|
48
|
+
"__version__",
|
|
49
|
+
"analysis",
|
|
50
|
+
"auth",
|
|
51
|
+
"cards",
|
|
52
|
+
"configure",
|
|
53
|
+
"errors",
|
|
54
|
+
"fetch",
|
|
55
|
+
"history",
|
|
56
|
+
"latest_year",
|
|
57
|
+
"load",
|
|
58
|
+
"lookup",
|
|
59
|
+
"pin",
|
|
60
|
+
"search",
|
|
61
|
+
"share",
|
|
62
|
+
"snapshot",
|
|
63
|
+
"types",
|
|
64
|
+
"unshare",
|
|
65
|
+
]
|
dfo/_client/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Client layer: the clients, the module-level functions and the command line."""
|