Coverage for session_buddy / utils / search / utilities.py: 51.09%

68 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-04 00:43 -0800

1"""Utility functions for advanced search. 

2 

3This module provides helper functions for content processing, time parsing, 

4and other search-related utilities. 

5""" 

6 

7from __future__ import annotations 

8 

9from contextlib import suppress 

10from datetime import UTC, datetime, timedelta 

11 

12from session_buddy.session_types import TimeRange 

13from session_buddy.utils.regex_patterns import SAFE_PATTERNS 

14 

15 

16def extract_technical_terms(content: str) -> list[str]: 

17 """Extract technical terms and patterns from content.""" 

18 terms = [] 

19 

20 # Programming language detection 

21 lang_pattern_names = [ 

22 "python_code", 

23 "javascript_code", 

24 "sql_code", 

25 "error_keywords", 

26 ] 

27 lang_mapping = { 

28 "python_code": "python", 

29 "javascript_code": "javascript", 

30 "sql_code": "sql", 

31 "error_keywords": "error", 

32 } 

33 

34 for pattern_name in lang_pattern_names: 

35 pattern = SAFE_PATTERNS[pattern_name] 

36 if pattern.search(content): 

37 terms.append(lang_mapping[pattern_name]) 

38 

39 # Extract function names 

40 func_pattern = SAFE_PATTERNS["function_definition"] 

41 func_matches = func_pattern.findall(content) 

42 terms.extend([f"function:{func}" for func in func_matches[:5]]) # Limit to 5 

43 

44 # Extract class names 

45 class_pattern = SAFE_PATTERNS["class_definition"] 

46 class_matches = class_pattern.findall(content) 

47 terms.extend([f"class:{cls}" for cls in class_matches[:5]]) 

48 

49 # Extract file extensions 

50 ext_pattern = SAFE_PATTERNS["file_extension"] 

51 file_matches = ext_pattern.findall(content) 

52 terms.extend([f"filetype:{ext}" for ext in set(file_matches[:10])]) 

53 

54 return terms[:20] # Limit total terms 

55 

56 

57def truncate_content(content: str, max_length: int = 500) -> str: 

58 """Truncate content to maximum length.""" 

59 return content[:max_length] + "..." if len(content) > max_length else content 

60 

61 

62def ensure_timezone(timestamp: datetime) -> datetime: 

63 """Ensure timestamp has timezone information.""" 

64 return timestamp.replace(tzinfo=UTC) if timestamp.tzinfo is None else timestamp 

65 

66 

67def parse_timeframe_single(timeframe: str) -> datetime | None: 

68 """Parse timeframe string into datetime.""" 

69 with suppress(ValueError): 

70 if timeframe.endswith("d"): 70 ↛ 73line 70 didn't jump to line 73 because the condition on line 70 was always true

71 days = int(timeframe[:-1]) 

72 return datetime.now(UTC) - timedelta(days=days) 

73 if timeframe.endswith("h"): 

74 hours = int(timeframe[:-1]) 

75 return datetime.now(UTC) - timedelta(hours=hours) 

76 if timeframe.endswith("w"): 

77 weeks = int(timeframe[:-1]) 

78 return datetime.now(UTC) - timedelta(weeks=weeks) 

79 if timeframe.endswith("m"): 

80 months = int(timeframe[:-1]) 

81 return datetime.now(UTC) - timedelta(days=months * 30) 

82 return None 

83 

84 

85def parse_timeframe(timeframe: str) -> TimeRange: 

86 """Parse timeframe string into TimeRange object. 

87 

88 Supports formats like: 

89 - '7d' (last 7 days) 

90 - '2024-01' (specific month) 

91 - '2024' (specific year) 

92 - '2024-01-01..2024-01-31' (date range) 

93 """ 

94 # Range format: 'start..end' 

95 if ".." in timeframe: 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true

96 parts = timeframe.split("..") 

97 start = datetime.fromisoformat(parts[0]).replace(tzinfo=UTC) 

98 end = datetime.fromisoformat(parts[1]).replace(tzinfo=UTC) 

99 return TimeRange(start=start, end=end) 

100 

101 # Relative timeframe: '7d', '2w', etc. 

102 if timeframe[-1] in "dhwm": 102 ↛ 109line 102 didn't jump to line 109 because the condition on line 102 was always true

103 end = datetime.now(UTC) 

104 relative_start: datetime | None = parse_timeframe_single(timeframe) 

105 if relative_start: 105 ↛ 109line 105 didn't jump to line 109 because the condition on line 105 was always true

106 return TimeRange(start=relative_start, end=end) 

107 

108 # Year only: '2024' 

109 if len(timeframe) == 4 and timeframe.isdigit(): 

110 year = int(timeframe) 

111 start = datetime(year, 1, 1, tzinfo=UTC) 

112 end = datetime(year + 1, 1, 1, tzinfo=UTC) 

113 return TimeRange(start=start, end=end) 

114 

115 # Year-month: '2024-01' 

116 if len(timeframe) == 7: 

117 year, month = map(int, timeframe.split("-")) 

118 start = datetime(year, month, 1, tzinfo=UTC) 

119 # Calculate next month 

120 if month == 12: 

121 end = datetime(year + 1, 1, 1, tzinfo=UTC) 

122 else: 

123 end = datetime(year, month + 1, 1, tzinfo=UTC) 

124 return TimeRange(start=start, end=end) 

125 

126 # Default: last 7 days 

127 end = datetime.now(UTC) 

128 start = end - timedelta(days=7) 

129 return TimeRange(start=start, end=end)