oda-data 2.4.0__tar.gz → 2.4.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 (69) hide show
  1. {oda_data-2.4.0 → oda_data-2.4.2}/PKG-INFO +1 -1
  2. {oda_data-2.4.0 → oda_data-2.4.2}/pyproject.toml +1 -1
  3. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/api/sources.py +42 -10
  4. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/clean_data/schema.py +2 -0
  5. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/cache.py +19 -0
  6. {oda_data-2.4.0 → oda_data-2.4.2}/README.md +0 -0
  7. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/__init__.py +0 -0
  8. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/api/__init__.py +0 -0
  9. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/api/constants.py +0 -0
  10. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/api/oecd.py +0 -0
  11. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/api/representations.py +0 -0
  12. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/clean_data/__init__.py +0 -0
  13. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/clean_data/channels.py +0 -0
  14. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/clean_data/common.py +0 -0
  15. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/clean_data/crs_channel_mapping.csv +0 -0
  16. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/clean_data/dtypes.py +0 -0
  17. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/clean_data/validation.py +0 -0
  18. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/config.py +0 -0
  19. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/__init__.py +0 -0
  20. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/common.py +0 -0
  21. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/crs/__init__.py +0 -0
  22. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/crs/common.py +0 -0
  23. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/crs/crs_functions.py +0 -0
  24. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/crs/crs_indicators.json +0 -0
  25. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/crs/oecd.py +0 -0
  26. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/crs/one.py +0 -0
  27. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac1/__init__.py +0 -0
  28. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac1/common.py +0 -0
  29. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac1/dac1_functions.py +0 -0
  30. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac1/dac1_indicators.json +0 -0
  31. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac1/oecd.py +0 -0
  32. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac1/one.py +0 -0
  33. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac2a/__init__.py +0 -0
  34. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac2a/common.py +0 -0
  35. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac2a/dac2a_functions.py +0 -0
  36. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac2a/dac2a_indicators.json +0 -0
  37. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac2a/oecd.py +0 -0
  38. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/dac2a/one.py +0 -0
  39. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/indicator.py +0 -0
  40. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/research/__init__.py +0 -0
  41. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/research/eu.py +0 -0
  42. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/research/policy_markers.py +0 -0
  43. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/indicators/research/sector_imputations.py +0 -0
  44. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/logger.py +0 -0
  45. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/oda_data_indicators.csv +0 -0
  46. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/crs_flow_types.json +0 -0
  47. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/crs_modalities.json +0 -0
  48. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/crs_policy_markers.json +0 -0
  49. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/crs_purpose.json +0 -0
  50. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/crs_type_of_finance.json +0 -0
  51. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/dac1_aid_flow_types.json +0 -0
  52. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/dac2a_aid_type.json +0 -0
  53. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/flow_types.json +0 -0
  54. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/indicators.json +0 -0
  55. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/key_columns.json +0 -0
  56. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/provider_groupings.json +0 -0
  57. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/recipient_groupings.json +0 -0
  58. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/settings/v1_indicators_mapping.json +0 -0
  59. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/__init__.py +0 -0
  60. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/compatibility.py +0 -0
  61. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/gni.py +0 -0
  62. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/groupings.py +0 -0
  63. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/names/__init__.py +0 -0
  64. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/names/add.py +0 -0
  65. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/names/create_mapping.py +0 -0
  66. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/names/crs_names.json +0 -0
  67. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/names/dac1_names.json +0 -0
  68. {oda_data-2.4.0 → oda_data-2.4.2}/src/oda_data/tools/names/dac2a_names.json +0 -0
  69. {oda_data-2.4.0 → oda_data-2.4.2}/src/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.4.0
3
+ Version: 2.4.2
4
4
  Summary: A python package to work with Official Development Assistance data from the OECD DAC.
5
5
  Author: Jorge Rivera, Miguel Haro Ruiz
6
6
  Author-email: Jorge Rivera <jorge.rivera@one.org>, Miguel Haro Ruiz <miguel.haroruiz@one.org>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oda_data"
3
- version = "2.4.0"
3
+ version = "2.4.2"
4
4
  description = "A python package to work with Official Development Assistance data from the OECD DAC."
5
5
  requires-python = ">=3.11"
6
6
 
@@ -37,6 +37,7 @@ from oda_data.tools.cache import (
37
37
  create_crs_bulk_fetcher,
38
38
  create_multisystem_bulk_fetcher,
39
39
  generate_param_hash,
40
+ generate_projection_hash,
40
41
  )
41
42
 
42
43
 
@@ -418,9 +419,20 @@ class DACSource(Source):
418
419
  # 1. Try memory cache (thread-safe, fastest)
419
420
  df = self.memory_cache.get(param_hash)
420
421
  if df is not None:
421
- logger.info("Cache hit: memory")
422
- # Data is already cleaned, just select columns if needed
423
- return self._apply_columns_and_clean(df, columns, already_cleaned=True)
422
+ # Only use if all requested columns are available
423
+ if not columns or all(c in df.columns for c in columns):
424
+ logger.info("Cache hit: memory")
425
+ return self._apply_columns_and_clean(
426
+ df, columns, already_cleaned=True
427
+ )
428
+
429
+ # For projected reads, also check a projection-specific memory cache
430
+ if columns:
431
+ proj_hash = generate_projection_hash(param_hash, columns)
432
+ df = self.memory_cache.get(proj_hash)
433
+ if df is not None:
434
+ logger.info("Cache hit: memory (projection)")
435
+ return df
424
436
 
425
437
  # 2. Try query cache (on-disk parquet, fast)
426
438
  df = self.query_cache.load(self.__class__.__name__, param_hash, None, columns)
@@ -435,9 +447,15 @@ class DACSource(Source):
435
447
 
436
448
  # 3. Cache miss (query/memory) - need to fetch data
437
449
  if using_bulk_download:
438
- # Fetch from bulk cache with predicate pushdown and column selection
439
450
  logger.info("Query cache miss - reading from bulk cache")
440
451
  df = self._fetch_from_bulk_cache(filters, columns)
452
+ if columns:
453
+ # Cache projected result under a projection-specific key
454
+ # so it doesn't pollute the shared filter-only cache
455
+ self._cache_in_memory(
456
+ generate_projection_hash(param_hash, columns), df
457
+ )
458
+ return df
441
459
  else:
442
460
  # Download via API (uses init-time filters only)
443
461
  logger.info("Cache miss - downloading via API")
@@ -801,9 +819,20 @@ class AidDataData(AidDataSource):
801
819
  # 1. Try memory cache
802
820
  df = self.memory_cache.get(param_hash)
803
821
  if df is not None:
804
- logger.info("Cache hit: memory")
805
- # Data is already cleaned, just select columns if needed
806
- return self._apply_columns_and_clean(df, columns, already_cleaned=True)
822
+ # Only use if all requested columns are available
823
+ if not columns or all(c in df.columns for c in columns):
824
+ logger.info("Cache hit: memory")
825
+ return self._apply_columns_and_clean(
826
+ df, columns, already_cleaned=True
827
+ )
828
+
829
+ # For projected reads, also check a projection-specific memory cache
830
+ if columns:
831
+ proj_hash = generate_projection_hash(param_hash, columns)
832
+ df = self.memory_cache.get(proj_hash)
833
+ if df is not None:
834
+ logger.info("Cache hit: memory (projection)")
835
+ return df
807
836
 
808
837
  # 2. Try query cache
809
838
  # Use column selection at parquet read level for efficiency
@@ -829,16 +858,19 @@ class AidDataData(AidDataSource):
829
858
  # Use PyArrow predicate pushdown and column selection for maximum efficiency
830
859
  pyarrow_filters = filters if filters else None
831
860
 
832
- # If columns specified, read directly without caching (targeted read)
861
+ # If columns specified, read directly with projection-specific caching
833
862
  if columns:
834
863
  logger.info(
835
864
  "Query cache miss - reading from bulk cache with column selection"
836
865
  )
837
- # Targeted read: use full PyArrow optimization, no caching
838
866
  df = pd.read_parquet(
839
867
  bulk_path, filters=pyarrow_filters, columns=columns, engine="pyarrow"
840
868
  )
841
- # Data is already cleaned and column-selected, return directly
869
+ # Cache under projection-specific key to avoid polluting
870
+ # the shared filter-only cache
871
+ self._cache_in_memory(
872
+ generate_projection_hash(param_hash, columns), df
873
+ )
842
874
  return df
843
875
  else:
844
876
  logger.info("Query cache miss - reading from bulk cache")
@@ -435,6 +435,8 @@ CRS_MAPPING: dict[str, str] = CORE_CRS_MAPPING | {
435
435
  "budget_ident": ODASchema.BUDGET_IDENTIFIER,
436
436
  "usd_psi_gr_eq_memo": ODASchema.PSI_GRANT_EQ_USD,
437
437
  "datatype_code": ODASchema.DATA_TYPE_CODE,
438
+ "donor": ODASchema.PROVIDER_NAME,
439
+ "recipient": ODASchema.RECIPIENT_NAME,
438
440
  }
439
441
 
440
442
 
@@ -50,6 +50,25 @@ def generate_param_hash(filters: list[tuple]) -> str:
50
50
  return hashlib.md5(json_str.encode("utf-8")).hexdigest()[:10]
51
51
 
52
52
 
53
+ def generate_projection_hash(param_hash: str, columns: list[str]) -> str:
54
+ """Generates a cache key for a projected (column-subset) read.
55
+
56
+ Combines the filter-based param_hash with a hash of the requested columns,
57
+ so that projected reads are cached separately from full reads without
58
+ polluting the shared filter-only cache key.
59
+
60
+ Args:
61
+ param_hash: The filter-only hash from generate_param_hash
62
+ columns: List of column names in the projection
63
+
64
+ Returns:
65
+ A combined hash string
66
+ """
67
+ col_str = json.dumps(sorted(columns), sort_keys=True)
68
+ col_hash = hashlib.md5(col_str.encode("utf-8")).hexdigest()[:10]
69
+ return f"{param_hash}_proj_{col_hash}"
70
+
71
+
53
72
  class ThreadSafeMemoryCache:
54
73
  """Thread-safe wrapper around cachetools.TTLCache.
55
74
 
File without changes