|
2 | 2 | import logging |
3 | 3 | import os |
4 | 4 | import tempfile |
5 | | -from functools import partial |
| 5 | +from functools import lru_cache, partial |
6 | 6 | from typing import Any |
7 | 7 |
|
8 | 8 | from goodmap.core import get_queried_data |
|
20 | 20 | # it should be replaced with dynamic solution |
21 | 21 |
|
22 | 22 |
|
| 23 | +# ------------------------------------------------ |
| 24 | +# Caching utilities for database operations |
| 25 | +# ------------------------------------------------ |
| 26 | + |
| 27 | + |
| 28 | +@lru_cache(maxsize=32) |
| 29 | +def _cached_read_json_file(file_path: str) -> str: |
| 30 | + """Read and cache a JSON file's raw content. |
| 31 | +
|
| 32 | + Uses lru_cache to avoid repeated disk I/O for frequently accessed files. |
| 33 | + Returns raw string (not dict) because lru_cache requires hashable, |
| 34 | + immutable return values to prevent cache corruption from mutations. |
| 35 | +
|
| 36 | + Args: |
| 37 | + file_path: Absolute path to the JSON file. |
| 38 | +
|
| 39 | + Returns: |
| 40 | + str: Raw file content. Caller must use json.loads() to parse. |
| 41 | + """ |
| 42 | + with open(file_path, "r") as file: |
| 43 | + return file.read() |
| 44 | + |
| 45 | + |
| 46 | +def _get_cached_json_data(file_path: str) -> dict[str, Any]: |
| 47 | + """Get parsed JSON data from cache, reading from disk only on cache miss. |
| 48 | +
|
| 49 | + Args: |
| 50 | + file_path: Absolute path to the JSON file. |
| 51 | +
|
| 52 | + Returns: |
| 53 | + dict: Parsed JSON data. Note: caller should not mutate the result. |
| 54 | + """ |
| 55 | + raw_data = _cached_read_json_file(file_path) |
| 56 | + return json.loads(raw_data) |
| 57 | + |
| 58 | + |
| 59 | +def clear_cache(): |
| 60 | + """Clear all JSON file caches. |
| 61 | +
|
| 62 | + Must be called after any file modification to prevent stale reads. |
| 63 | + Automatically invoked by json_file_atomic_dump(). |
| 64 | + """ |
| 65 | + _cached_read_json_file.cache_clear() |
| 66 | + logger.debug("Database caches cleared") |
| 67 | + |
| 68 | + |
23 | 69 | def __parse_pagination_params(query): |
24 | 70 | """Extract and validate pagination parameters from query.""" |
25 | 71 | try: |
@@ -68,6 +114,8 @@ def json_file_atomic_dump(data, file_path): |
68 | 114 | temp_file.flush() |
69 | 115 | os.fsync(temp_file.fileno()) |
70 | 116 | os.replace(temp_file.name, file_path) |
| 117 | + # Clear cache after file write to ensure fresh data on next read |
| 118 | + clear_cache() |
71 | 119 |
|
72 | 120 |
|
73 | 121 | class PaginationHelper: |
@@ -521,8 +569,9 @@ def json_db_get_categories(self): |
521 | 569 |
|
522 | 570 |
|
523 | 571 | def json_file_db_get_categories(self): |
524 | | - with open(self.data_file_path, "r") as file: |
525 | | - return json.load(file)["map"]["categories"].keys() |
| 572 | + """Retrieve category names from JSON file database (cached).""" |
| 573 | + data = _get_cached_json_data(self.data_file_path) |
| 574 | + return data["map"]["categories"].keys() |
526 | 575 |
|
527 | 576 |
|
528 | 577 | def google_json_db_get_categories(self): |
@@ -561,21 +610,29 @@ def json_db_get_category_data(self, category_type=None): |
561 | 610 |
|
562 | 611 |
|
563 | 612 | def json_file_db_get_category_data(self, category_type=None): |
564 | | - with open(self.data_file_path, "r") as file: |
565 | | - data = json.load(file)["map"] |
566 | | - if category_type: |
567 | | - return { |
568 | | - "categories": {category_type: data["categories"].get(category_type, [])}, |
569 | | - "categories_help": data.get("categories_help", []), |
570 | | - "categories_options_help": { |
571 | | - category_type: data.get("categories_options_help", {}).get(category_type, []) |
572 | | - }, |
573 | | - } |
| 613 | + """Retrieve category data from JSON file database (cached). |
| 614 | +
|
| 615 | + Args: |
| 616 | + category_type: If provided, return data for this category only. |
| 617 | + If None, return all categories. |
| 618 | +
|
| 619 | + Returns: |
| 620 | + dict: Contains 'categories', 'categories_help', and 'categories_options_help'. |
| 621 | + """ |
| 622 | + data = _get_cached_json_data(self.data_file_path)["map"] |
| 623 | + if category_type: |
574 | 624 | return { |
575 | | - "categories": data["categories"], |
| 625 | + "categories": {category_type: data["categories"].get(category_type, [])}, |
576 | 626 | "categories_help": data.get("categories_help", []), |
577 | | - "categories_options_help": data.get("categories_options_help", {}), |
| 627 | + "categories_options_help": { |
| 628 | + category_type: data.get("categories_options_help", {}).get(category_type, []) |
| 629 | + }, |
578 | 630 | } |
| 631 | + return { |
| 632 | + "categories": data["categories"], |
| 633 | + "categories_help": data.get("categories_help", []), |
| 634 | + "categories_options_help": data.get("categories_options_help", {}), |
| 635 | + } |
579 | 636 |
|
580 | 637 |
|
581 | 638 | def google_json_db_get_category_data(self, category_type=None): |
|
0 commit comments