utils.py 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import os
  2. import sys
  3. from functools import lru_cache
  4. from pathlib import Path
  5. from typing import Any
  6. class TrieNode:
  7. def __init__(self, config_file: str = "", config_data: dict[str, Any] | None = None) -> None:
  8. if not config_data:
  9. config_data = {}
  10. self.nodes: dict[str, TrieNode] = {}
  11. self.config_info: tuple[str, dict[str, Any]] = (config_file, config_data)
  12. class Trie:
  13. """
  14. A prefix tree to store the paths of all config files and to search the nearest config
  15. associated with each file
  16. """
  17. def __init__(self, config_file: str = "", config_data: dict[str, Any] | None = None) -> None:
  18. self.root: TrieNode = TrieNode(config_file, config_data)
  19. def insert(self, config_file: str, config_data: dict[str, Any]) -> None:
  20. resolved_config_path_as_tuple = Path(config_file).parent.resolve().parts
  21. temp = self.root
  22. for path in resolved_config_path_as_tuple:
  23. if path not in temp.nodes:
  24. temp.nodes[path] = TrieNode()
  25. temp = temp.nodes[path]
  26. temp.config_info = (config_file, config_data)
  27. def search(self, filename: str) -> tuple[str, dict[str, Any]]:
  28. """
  29. Returns the closest config relative to filename by doing a depth
  30. first search on the prefix tree.
  31. """
  32. resolved_file_path_as_tuple = Path(filename).resolve().parts
  33. temp = self.root
  34. last_stored_config: tuple[str, dict[str, Any]] = ("", {})
  35. for path in resolved_file_path_as_tuple:
  36. if temp.config_info[0]:
  37. last_stored_config = temp.config_info
  38. if path not in temp.nodes:
  39. break
  40. temp = temp.nodes[path]
  41. return last_stored_config
  42. @lru_cache(maxsize=1000)
  43. def exists_case_sensitive(path: str) -> bool:
  44. """Returns if the given path exists and also matches the case on Windows.
  45. When finding files that can be imported, it is important for the cases to match because while
  46. file os.path.exists("module.py") and os.path.exists("MODULE.py") both return True on Windows,
  47. Python can only import using the case of the real file.
  48. """
  49. result = os.path.exists(path)
  50. if result and (sys.platform.startswith("win") or sys.platform == "darwin"): # pragma: no cover
  51. directory, basename = os.path.split(path)
  52. result = basename in os.listdir(directory)
  53. return result