oda-data 2.0.0__tar.gz → 2.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {oda_data-2.0.0 → oda_data-2.0.2}/PKG-INFO +3 -3
  2. {oda_data-2.0.0 → oda_data-2.0.2}/README.md +2 -2
  3. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/__init__.py +5 -2
  4. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/api/oecd.py +5 -5
  5. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/api/representations.py +2 -2
  6. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/api/sources.py +69 -27
  7. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac1/dac1_functions.py +31 -0
  8. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/research/eu.py +9 -9
  9. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/cache.py +3 -3
  10. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/gni.py +4 -4
  11. {oda_data-2.0.0 → oda_data-2.0.2}/pyproject.toml +1 -1
  12. {oda_data-2.0.0 → oda_data-2.0.2}/LICENSE +0 -0
  13. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/.raw_data/README.md +0 -0
  14. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/api/__init__.py +0 -0
  15. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/api/constants.py +0 -0
  16. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/clean_data/__init__.py +0 -0
  17. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/clean_data/channels.py +0 -0
  18. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/clean_data/common.py +0 -0
  19. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/clean_data/crs_channel_mapping.csv +0 -0
  20. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/clean_data/dtypes.py +0 -0
  21. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/clean_data/schema.py +0 -0
  22. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/clean_data/validation.py +0 -0
  23. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/config.py +0 -0
  24. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/__init__.py +0 -0
  25. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/common.py +0 -0
  26. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/crs/__init__.py +0 -0
  27. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/crs/common.py +0 -0
  28. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/crs/crs_functions.py +0 -0
  29. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/crs/crs_indicators.json +0 -0
  30. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/crs/oecd.py +0 -0
  31. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/crs/one.py +0 -0
  32. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac1/__init__.py +0 -0
  33. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac1/common.py +0 -0
  34. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac1/dac1_indicators.json +0 -0
  35. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac1/oecd.py +0 -0
  36. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac1/one.py +0 -0
  37. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac2a/__init__.py +0 -0
  38. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac2a/common.py +0 -0
  39. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac2a/dac2a_functions.py +0 -0
  40. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac2a/dac2a_indicators.json +0 -0
  41. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac2a/oecd.py +0 -0
  42. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/dac2a/one.py +0 -0
  43. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/indicator.py +0 -0
  44. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/research/__init__.py +0 -0
  45. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/research/policy_markers.py +0 -0
  46. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/indicators/research/sector_imputations.py +0 -0
  47. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/logger.py +0 -0
  48. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/crs_flow_types.json +0 -0
  49. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/crs_modalities.json +0 -0
  50. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/crs_policy_markers.json +0 -0
  51. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/crs_purpose.json +0 -0
  52. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/crs_type_of_finance.json +0 -0
  53. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/dac1_aid_flow_types.json +0 -0
  54. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/dac2a_aid_type.json +0 -0
  55. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/flow_types.json +0 -0
  56. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/indicators.json +0 -0
  57. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/key_columns.json +0 -0
  58. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/provider_groupings.json +0 -0
  59. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/recipient_groupings.json +0 -0
  60. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/settings/v1_indicators_mapping.json +0 -0
  61. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/__init__.py +0 -0
  62. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/compatibility.py +0 -0
  63. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/groupings.py +0 -0
  64. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/names/__init__.py +0 -0
  65. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/names/add.py +0 -0
  66. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/names/create_mapping.py +0 -0
  67. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/names/crs_names.json +0 -0
  68. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/names/dac1_names.json +0 -0
  69. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/names/dac2a_names.json +0 -0
  70. {oda_data-2.0.0 → oda_data-2.0.2}/oda_data/tools/sector_lists.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: oda_data
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: A python package to work with Official Development Assistance data from the OECD DAC.
5
5
  Author: Jorge Rivera
6
6
  Author-email: jorge.rivera@one.org
@@ -49,10 +49,10 @@ the [issues page](https://github.com/ONEcampaign/oda_data_package/issues).
49
49
 
50
50
  ### Installation
51
51
 
52
- The beta version of the package can be installed using pip:
52
+ The latest version of the package can be installed using pip:
53
53
 
54
54
  ```bash
55
- pip install --pre oda-data --upgrade
55
+ pip install oda-data --upgrade
56
56
  ```
57
57
 
58
58
  The package is compatible with Python 3.10 and above.
@@ -22,10 +22,10 @@ the [issues page](https://github.com/ONEcampaign/oda_data_package/issues).
22
22
 
23
23
  ### Installation
24
24
 
25
- The beta version of the package can be installed using pip:
25
+ The latest version of the package can be installed using pip:
26
26
 
27
27
  ```bash
28
- pip install --pre oda-data --upgrade
28
+ pip install oda-data --upgrade
29
29
  ```
30
30
 
31
31
  The package is compatible with Python 3.10 and above.
@@ -3,7 +3,7 @@ from importlib.metadata import version
3
3
  __version__ = version("oda_data")
4
4
 
5
5
  from oda_data import tools
6
- from oda_reader import set_cache_dir
6
+ from oda_reader import set_cache_dir, disable_cache, enable_cache, clear_cache
7
7
  from oda_data.api.oecd import OECDClient
8
8
  from oda_data.api.sources import DAC1Data, DAC2AData, MultiSystemData, CRSData
9
9
  from oda_data.indicators.research.policy_markers import bilateral_policy_marker
@@ -20,7 +20,7 @@ def set_data_path(path):
20
20
  from pydeflate import set_pydeflate_path
21
21
 
22
22
  """Set the path to the data folder."""
23
- global OdaPATHS
23
+ global ODAPaths
24
24
 
25
25
  ODAPaths.raw_data = Path(path).resolve()
26
26
  set_pydeflate_path(ODAPaths.raw_data)
@@ -43,4 +43,7 @@ __all__ = [
43
43
  "add_names_columns",
44
44
  "add_gni_share_column",
45
45
  "set_cache_dir",
46
+ "disable_cache",
47
+ "enable_cache",
48
+ "clear_cache",
46
49
  ]
@@ -13,7 +13,7 @@ from oda_data.api.constants import (
13
13
  PRICES,
14
14
  _EXCLUDE,
15
15
  )
16
- from oda_data.api.representations import _OdaDict, _OdaList
16
+ from oda_data.api.representations import _ODADict, _ODAList
17
17
  from oda_data.api.sources import DAC1Data, DAC2AData, CRSData, MultiSystemData
18
18
  from oda_data.clean_data import common as clean
19
19
  from oda_data.clean_data.validation import validate_currency, validate_measure
@@ -228,18 +228,18 @@ class OECDClient:
228
228
  def available_providers(cls) -> dict:
229
229
  """Returns a dictionary of available donor codes and their names"""
230
230
  logger.info("Note that not all providers may be available for all indicators")
231
- return _OdaDict(provider_groupings()["all_official"])
231
+ return _ODADict(provider_groupings()["all_official"])
232
232
 
233
233
  @classmethod
234
234
  def available_recipients(cls) -> dict:
235
235
  """Returns a dictionary of available recipient codes"""
236
236
  logger.info("Note that not all recipients may be available for all indicators")
237
- return _OdaDict(recipient_groupings()["all_developing_countries_regions"])
237
+ return _ODADict(recipient_groupings()["all_developing_countries_regions"])
238
238
 
239
239
  @classmethod
240
240
  def available_currencies(cls) -> list:
241
241
  """Returns a dictionary of available currencies"""
242
- return _OdaList(CURRENCIES)
242
+ return _ODAList(CURRENCIES)
243
243
 
244
244
  @classmethod
245
245
  def available_indicators(cls) -> dict:
@@ -250,7 +250,7 @@ class OECDClient:
250
250
  n: v for n, v in i.items() if n in ["name", "description", "sources"]
251
251
  }
252
252
 
253
- return _OdaDict(indicators)
253
+ return _ODADict(indicators)
254
254
 
255
255
  @classmethod
256
256
  def export_available_indicators(cls, export_folder: str | Path) -> None:
@@ -1,10 +1,10 @@
1
- class _OdaList(list):
1
+ class _ODAList(list):
2
2
  def __repr__(self):
3
3
  # Return the elements with a line break between them
4
4
  return "[\n" + ",\n".join(str(x) for x in self) + "\n]"
5
5
 
6
6
 
7
- class _OdaDict(dict):
7
+ class _ODADict(dict):
8
8
  def __repr__(self):
9
9
  # Return the elements with a line break between them
10
10
  return "{\n" + ",\n".join(f"{k}: {v}" for k, v in self.items()) + "\n}"
@@ -2,6 +2,7 @@ from abc import abstractmethod
2
2
  from typing import Optional
3
3
 
4
4
  import pandas as pd
5
+ from cachetools import TTLCache
5
6
  from oda_reader import (
6
7
  download_crs,
7
8
  bulk_download_crs,
@@ -10,6 +11,7 @@ from oda_reader import (
10
11
  bulk_download_multisystem,
11
12
  download_multisystem,
12
13
  )
14
+ from oda_reader._cache import memory
13
15
 
14
16
  from oda_data import config
15
17
  from oda_data.clean_data.common import (
@@ -23,6 +25,7 @@ from oda_data.clean_data.validation import (
23
25
  check_strings,
24
26
  )
25
27
  from oda_data.logger import logger
28
+ from oda_data.tools.cache import OnDiskCache, generate_param_hash
26
29
 
27
30
 
28
31
  def _filters_to_query(filters: list[tuple[str, str, list]]) -> str:
@@ -45,13 +48,15 @@ class DACSource:
45
48
  of data in the form of a pandas DataFrame.
46
49
  """
47
50
 
51
+ memory_cache = TTLCache(maxsize=20, ttl=6000)
52
+ disk_cache = OnDiskCache(config.ODAPaths.raw_data, ttl_seconds=86400)
53
+
48
54
  def __init__(self):
49
55
  self.de_providers = None
50
56
  self.de_recipients = None
51
57
  self.de_indicators = None
52
58
  self.filters = None
53
59
  self.de_sectors = None
54
- self.refresh_data = False
55
60
 
56
61
  def _init_filters(
57
62
  self,
@@ -168,25 +173,48 @@ class DACSource:
168
173
  f"Missing values in {column}: {', '.join(map(str, missing_values))}"
169
174
  )
170
175
 
171
- def _check_file_is_available(
172
- self,
173
- dataset: str,
174
- using_bulk_download: bool = False,
175
- filters: list[tuple] = None,
176
- ) -> None:
177
- """Checks if a dataset file is available locally, triggering a download if not.
176
+ def _get_read_filters(
177
+ self, additional_filters: Optional[list[tuple]] = None
178
+ ) -> list[tuple[str, str, list]] | None:
179
+ filters = (
180
+ self.filters + additional_filters if additional_filters else self.filters
181
+ )
182
+ return filters or None
178
183
 
179
- Args:
180
- dataset (str): The dataset name.
181
- using_bulk_download (bool, optional): Whether to check for a filtered version. Defaults to False.
182
- """
184
+ def _get_cached_dataset(
185
+ self, param_hash: str, filters: list[tuple], columns: list[str]
186
+ ) -> pd.DataFrame | None:
183
187
 
184
- prefix = "full" if using_bulk_download else "filtered"
188
+ cached_df = self.memory_cache.get(param_hash)
185
189
 
186
- if not (config.ODAPaths.raw_data / f"{prefix}{dataset}.parquet").exists():
187
- logger.info(f"{dataset} data not found. Downloading...")
188
- df = self.download(bulk=using_bulk_download).pipe(clean_raw_df)
189
- df.to_parquet(config.ODAPaths.raw_data / f"{prefix}{dataset}.parquet")
190
+ if cached_df is None:
191
+ cached_df = self.disk_cache.load(
192
+ self.__class__.__name__, param_hash, filters, columns
193
+ )
194
+
195
+ return cached_df
196
+
197
+ def _cache_dataset(self, param_hash: str, df: pd.DataFrame) -> None:
198
+ """Caches the dataset in memory and on disk."""
199
+ self.memory_cache[param_hash] = df.copy(deep=True)
200
+ self.disk_cache.save(self.__class__.__name__, param_hash, df)
201
+
202
+ def _apply_query_and_cache(
203
+ self, df: pd.DataFrame, query: str | None, param_hash: str, bulk: bool
204
+ ):
205
+ if bulk:
206
+ self._cache_dataset(param_hash=param_hash, df=df)
207
+
208
+ if query:
209
+ df = df.query(query)
210
+
211
+ if not bulk:
212
+ self._cache_dataset(param_hash=param_hash, df=df)
213
+
214
+ return df
215
+
216
+ def _bulk_hash(self) -> str:
217
+ return generate_param_hash(filters=[("bulk", "==", self.__class__.__name__)])
190
218
 
191
219
  def read(
192
220
  self,
@@ -205,21 +233,35 @@ class DACSource:
205
233
  pd.DataFrame: The loaded dataset.
206
234
  """
207
235
  # Create filters
208
- filters = (
209
- self.filters + additional_filters
210
- if additional_filters is not None
211
- else self.filters
236
+ filters = self._get_read_filters(additional_filters=additional_filters)
237
+
238
+ # Generate a query string from the filters
239
+ query = _filters_to_query(filters) if filters else None
240
+
241
+ # Generate a hash string with the filters and the type of file
242
+ param_hash = (
243
+ self._bulk_hash()
244
+ if using_bulk_download
245
+ else generate_param_hash(filters=filters)
212
246
  )
213
247
 
214
- if len(filters) == 0:
215
- filters = None
248
+ # If caching is not active, clear the cache
249
+ if not memory().store_backend:
250
+ self.disk_cache.cleanup(hash_str=param_hash, force=True)
251
+ self.memory_cache.clear()
216
252
 
217
- query = _filters_to_query(filters) if filters else None
253
+ # Check if the data is already cached in memory or on disk
254
+ df = self._get_cached_dataset(
255
+ param_hash=param_hash, filters=filters, columns=columns
256
+ )
218
257
 
219
- df = self.download(bulk=using_bulk_download).pipe(clean_raw_df)
258
+ # If not cached, download the data
259
+ if df is None:
260
+ df = self.download(bulk=using_bulk_download).pipe(clean_raw_df)
261
+ df = self._apply_query_and_cache(
262
+ df=df, query=query, param_hash=param_hash, bulk=using_bulk_download
263
+ )
220
264
 
221
- if query:
222
- df = df.query(query)
223
265
  if columns:
224
266
  df = df.filter(columns)
225
267
 
@@ -23,6 +23,37 @@ def official_oda(data: pd.DataFrame) -> pd.DataFrame:
23
23
  return data.query(oda_query)
24
24
 
25
25
 
26
+ def official_oda_gni(data: pd.DataFrame) -> pd.DataFrame:
27
+ """Filters the dataset to include only Official Development Assistance (ODA)
28
+ and computes the ratio of ODA to Gross National Income (GNI).
29
+
30
+ Args:
31
+ data: A pandas DataFrame containing aid data.
32
+
33
+ Returns:
34
+ A filtered pandas DataFrame with only official ODA rows.
35
+ """
36
+ oda = official_oda(data)
37
+
38
+ gni = (
39
+ data.query("aidtype_code == 1")
40
+ .rename(columns={ODASchema.VALUE: "gni_value"})
41
+ .filter([ODASchema.YEAR, ODASchema.PROVIDER_CODE, "gni_value"])
42
+ )
43
+
44
+ df = oda.merge(gni, on=[ODASchema.YEAR, ODASchema.PROVIDER_CODE], how="left")
45
+ df[ODASchema.VALUE] = round(df[ODASchema.VALUE] / df["gni_value"], 6)
46
+
47
+ df = df.drop(columns=["gni_value"])
48
+
49
+ df[ODASchema.AIDTYPE_CODE] = 1010110101
50
+ df[ODASchema.AIDTYPE_NAME] = (
51
+ "Official Development Assistance, as a share of GNI (official definition)"
52
+ )
53
+
54
+ return df
55
+
56
+
26
57
  def one_core_oda(data: pd.DataFrame) -> pd.DataFrame:
27
58
  """Computes ONE's Core ODA metric: ODA excluding in-donor spending
28
59
  but including administrative costs.
@@ -115,28 +115,28 @@ def get_eui_oda_weights(
115
115
 
116
116
 
117
117
  def get_eui_plus_bilateral_providers_indicator(
118
- indicators_obj: OECDClient, indicator: str | list[str]
118
+ client_obj: OECDClient, indicator: str | list[str]
119
119
  ) -> pd.DataFrame:
120
120
  """Fetches indicator values with adjusted EU institution contributions.
121
121
 
122
122
  Args:
123
- indicators_obj: An `OECDClient` instance to fetch indicator data.
123
+ client_obj: An `OECDClient` instance to fetch indicator data.
124
124
  indicator: Indicator code or list of codes.
125
125
 
126
126
  Returns:
127
127
  A DataFrame containing indicator values with adjusted EU contributions.
128
128
  """
129
129
  eui_weights = get_eui_oda_weights(
130
- years=indicators_obj.years,
131
- providers=indicators_obj.providers,
132
- measure=indicators_obj.measure[0],
133
- use_bulk_download=indicators_obj.use_bulk_download,
130
+ years=client_obj.years,
131
+ providers=client_obj.providers,
132
+ measure=client_obj.measure[0],
133
+ use_bulk_download=client_obj.use_bulk_download,
134
134
  )
135
135
 
136
- if 918 not in indicators_obj.providers:
137
- indicators_obj.providers.append(918)
136
+ if 918 not in client_obj.providers:
137
+ client_obj.providers.append(918)
138
138
 
139
- data = indicators_obj.get_indicators(indicator)
139
+ data = client_obj.get_indicators(indicator)
140
140
  eui_mask = data[ODASchema.PROVIDER_CODE] == 918
141
141
 
142
142
  data.loc[eui_mask, ODASchema.VALUE] = data.loc[
@@ -37,7 +37,7 @@ class OnDiskCache:
37
37
 
38
38
  def get_file_path(self, dataset_name: str, param_hash: str) -> Path:
39
39
  """Returns the on-disk path for the given dataset and parameter hash."""
40
- return self.base_dir / f"filtered{dataset_name}-{param_hash}.parquet"
40
+ return self.base_dir / f"{dataset_name}-{param_hash}.parquet"
41
41
 
42
42
  def load(
43
43
  self,
@@ -65,9 +65,9 @@ class OnDiskCache:
65
65
 
66
66
  def cleanup(self, hash_str: Optional[str] = None, force: bool = False):
67
67
  """Removes expired cache files."""
68
- pattern = f"filtered*{hash_str}*.parquet" if hash_str else "filtered*.parquet"
68
+ pattern = f"*{hash_str}*.parquet"
69
69
 
70
70
  for file in self.base_dir.glob(pattern):
71
71
  if self._is_expired(file) or force:
72
72
  file.unlink(missing_ok=True)
73
- logger.info(f"Deleted expired cache file: {file}")
73
+ logger.info(f"Deleted cache file: {file}")
@@ -36,22 +36,22 @@ def _get_eu27_gni_as_eu_institutions(gni_obj: OECDClient) -> pd.DataFrame:
36
36
  )
37
37
 
38
38
 
39
- def _get_gni_data(indicators_obj: OECDClient) -> pd.DataFrame:
39
+ def _get_gni_data(client_obj: OECDClient) -> pd.DataFrame:
40
40
  """Fetch GNI data and optionally include EU Institutions' aggregate.
41
41
 
42
42
  Args:
43
- indicators_obj: An OECDClient object for the main data context.
43
+ client_obj: An OECDClient object for the main data context.
44
44
 
45
45
  Returns:
46
46
  A DataFrame with GNI values per provider and year.
47
47
  """
48
48
  # Create a copy of the indicators object and set the measure to "net_disbursement"
49
- gni_obj = copy(indicators_obj)
49
+ gni_obj = copy(client_obj)
50
50
  gni_obj.measure = ["net_disbursement"]
51
51
 
52
52
  # Fetch GNI data for the specified indicators
53
53
  gni_df = gni_obj.get_indicators("DAC1.40.1")
54
- providers = indicators_obj.providers or []
54
+ providers = client_obj.providers or []
55
55
 
56
56
  # Check if EU Institutions (provider code 918) is in the list of providers
57
57
  # If it is, aggregate GNI for EU27 countries and assign it to provider 918
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oda_data"
3
- version = "2.0.0"
3
+ version = "2.0.2"
4
4
  description = "A python package to work with Official Development Assistance data from the OECD DAC."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Jorge Rivera", email = "jorge.rivera@one.org" }, { name = "Miguel Haro Ruiz", email = "miguel.haroruiz@one.org" }]
File without changes
File without changes
File without changes