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.
Files changed (84) hide show
  1. development_finance-0.1.0.dist-info/METADATA +307 -0
  2. development_finance-0.1.0.dist-info/RECORD +84 -0
  3. development_finance-0.1.0.dist-info/WHEEL +4 -0
  4. development_finance-0.1.0.dist-info/entry_points.txt +3 -0
  5. dfo/__init__.py +65 -0
  6. dfo/_client/__init__.py +1 -0
  7. dfo/_client/analysis.py +234 -0
  8. dfo/_client/cards.py +94 -0
  9. dfo/_client/cli.py +160 -0
  10. dfo/_client/client.py +499 -0
  11. dfo/_client/default.py +348 -0
  12. dfo/_config/__init__.py +1 -0
  13. dfo/_config/credentials.py +145 -0
  14. dfo/_config/keys.py +344 -0
  15. dfo/_config/paths.py +119 -0
  16. dfo/_core/__init__.py +1 -0
  17. dfo/_core/codes.py +78 -0
  18. dfo/_core/errors.py +446 -0
  19. dfo/_core/json_text.py +66 -0
  20. dfo/_core/lookup_results.py +319 -0
  21. dfo/_core/nearest.py +45 -0
  22. dfo/_core/parse.py +509 -0
  23. dfo/_core/periods.py +79 -0
  24. dfo/_core/refs.py +787 -0
  25. dfo/_core/refusals.py +337 -0
  26. dfo/_core/text.py +323 -0
  27. dfo/_core/types.py +791 -0
  28. dfo/_datasets/__init__.py +1 -0
  29. dfo/_datasets/analysis.py +660 -0
  30. dfo/_datasets/build.py +753 -0
  31. dfo/_datasets/bundle.py +823 -0
  32. dfo/_datasets/cache.py +439 -0
  33. dfo/_datasets/cards.py +333 -0
  34. dfo/_datasets/dataset.py +1710 -0
  35. dfo/_datasets/fetch.py +763 -0
  36. dfo/_datasets/interrupt.py +31 -0
  37. dfo/_datasets/operation.py +567 -0
  38. dfo/_datasets/pin.py +109 -0
  39. dfo/_datasets/progress.py +160 -0
  40. dfo/_datasets/publication.py +667 -0
  41. dfo/_datasets/recipe.py +595 -0
  42. dfo/_datasets/resolve.py +1199 -0
  43. dfo/_datasets/shares.py +381 -0
  44. dfo/_generated/__init__.py +1 -0
  45. dfo/_generated/error_map.py +288 -0
  46. dfo/_generated/file_selector.py +10 -0
  47. dfo/_generated/reading_order.py +25 -0
  48. dfo/_generated/role_keys.py +18 -0
  49. dfo/_generated/strict.py +69 -0
  50. dfo/_generated/wire.py +3765 -0
  51. dfo/_http/__init__.py +1 -0
  52. dfo/_http/hooks.py +199 -0
  53. dfo/_http/transport.py +1442 -0
  54. dfo/_search/__init__.py +1 -0
  55. dfo/_search/answer.py +837 -0
  56. dfo/_search/answer_json.py +126 -0
  57. dfo/_search/display.py +256 -0
  58. dfo/_search/lookups.py +221 -0
  59. dfo/_search/search.py +46 -0
  60. dfo/_sync/__init__.py +1 -0
  61. dfo/_sync/_client/__init__.py +1 -0
  62. dfo/_sync/_client/client.py +498 -0
  63. dfo/_sync/_datasets/__init__.py +1 -0
  64. dfo/_sync/_datasets/analysis.py +655 -0
  65. dfo/_sync/_datasets/cards.py +334 -0
  66. dfo/_sync/_datasets/fetch.py +764 -0
  67. dfo/_sync/_datasets/operation.py +564 -0
  68. dfo/_sync/_datasets/pin.py +110 -0
  69. dfo/_sync/_datasets/resolve.py +1198 -0
  70. dfo/_sync/_datasets/shares.py +378 -0
  71. dfo/_sync/_http/__init__.py +1 -0
  72. dfo/_sync/_http/transport.py +1441 -0
  73. dfo/_sync/_search/__init__.py +1 -0
  74. dfo/_sync/_search/answer.py +838 -0
  75. dfo/_sync/_search/lookups.py +220 -0
  76. dfo/_sync/_search/search.py +47 -0
  77. dfo/_version.py +3 -0
  78. dfo/analysis.py +37 -0
  79. dfo/auth.py +19 -0
  80. dfo/cards.py +13 -0
  81. dfo/errors.py +65 -0
  82. dfo/lookup.py +18 -0
  83. dfo/py.typed +1 -0
  84. 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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.13.8
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ dfo = dfo._client.cli:main
3
+
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
+ ]
@@ -0,0 +1 @@
1
+ """Client layer: the clients, the module-level functions and the command line."""