cloudnoteslib 0.1.0__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 (33) hide show
  1. cloudnoteslib-0.1.0/PKG-INFO +37 -0
  2. cloudnoteslib-0.1.0/README.md +18 -0
  3. cloudnoteslib-0.1.0/cloudnoteslib/__init__.py +128 -0
  4. cloudnoteslib-0.1.0/cloudnoteslib/analyzers/__init__.py +14 -0
  5. cloudnoteslib-0.1.0/cloudnoteslib/analyzers/content_analyzer.py +180 -0
  6. cloudnoteslib-0.1.0/cloudnoteslib/analyzers/search.py +143 -0
  7. cloudnoteslib-0.1.0/cloudnoteslib/analyzers/statistics.py +88 -0
  8. cloudnoteslib-0.1.0/cloudnoteslib/config.py +28 -0
  9. cloudnoteslib-0.1.0/cloudnoteslib/exceptions.py +19 -0
  10. cloudnoteslib-0.1.0/cloudnoteslib/exporters/__init__.py +11 -0
  11. cloudnoteslib-0.1.0/cloudnoteslib/exporters/base.py +31 -0
  12. cloudnoteslib-0.1.0/cloudnoteslib/exporters/json_exporter.py +19 -0
  13. cloudnoteslib-0.1.0/cloudnoteslib/exporters/markdown_exporter.py +28 -0
  14. cloudnoteslib-0.1.0/cloudnoteslib/models/__init__.py +18 -0
  15. cloudnoteslib-0.1.0/cloudnoteslib/models/note.py +323 -0
  16. cloudnoteslib-0.1.0/cloudnoteslib/models/note_collection.py +233 -0
  17. cloudnoteslib-0.1.0/cloudnoteslib/models/tag.py +129 -0
  18. cloudnoteslib-0.1.0/cloudnoteslib/processors/__init__.py +36 -0
  19. cloudnoteslib-0.1.0/cloudnoteslib/processors/base.py +157 -0
  20. cloudnoteslib-0.1.0/cloudnoteslib/processors/markdown_processor.py +157 -0
  21. cloudnoteslib-0.1.0/cloudnoteslib/processors/plaintext_processor.py +103 -0
  22. cloudnoteslib-0.1.0/cloudnoteslib/processors/richtext_processor.py +122 -0
  23. cloudnoteslib-0.1.0/cloudnoteslib/security/__init__.py +12 -0
  24. cloudnoteslib-0.1.0/cloudnoteslib/security/encryptor.py +81 -0
  25. cloudnoteslib-0.1.0/cloudnoteslib/security/sanitizer.py +56 -0
  26. cloudnoteslib-0.1.0/cloudnoteslib.egg-info/PKG-INFO +37 -0
  27. cloudnoteslib-0.1.0/cloudnoteslib.egg-info/SOURCES.txt +31 -0
  28. cloudnoteslib-0.1.0/cloudnoteslib.egg-info/dependency_links.txt +1 -0
  29. cloudnoteslib-0.1.0/cloudnoteslib.egg-info/requires.txt +1 -0
  30. cloudnoteslib-0.1.0/cloudnoteslib.egg-info/top_level.txt +1 -0
  31. cloudnoteslib-0.1.0/pyproject.toml +25 -0
  32. cloudnoteslib-0.1.0/setup.cfg +4 -0
  33. cloudnoteslib-0.1.0/setup.py +25 -0
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloudnoteslib
3
+ Version: 0.1.0
4
+ Summary: A reusable Object-Oriented generic library for note processing, analysis, and security.
5
+ Home-page: https://github.com/Kavyavegunta04/Cloudnote
6
+ Author: Kavya
7
+ Author-email: Kavya <kavyavegunta27@gmail.com>
8
+ Project-URL: Homepage, https://github.com/Kavyavegunta04/Cloudnote
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Developers
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: cryptography>=41.0.0
16
+ Dynamic: author
17
+ Dynamic: home-page
18
+ Dynamic: requires-python
19
+
20
+ # cloudnoteslib
21
+
22
+ A comprehensive, reusable Python Object-Oriented Programming (OOP) library designed for text note processing, content analysis, security, and exports.
23
+
24
+ ## Features & OOP Principles
25
+ - **Encapsulation:** Strongly typed `Note` and `Tag` models with data validation via `@property`.
26
+ - **Abstraction:** Abstract `NoteProcessor` base class guaranteeing unified interfaces.
27
+ - **Inheritance & Polymorphism:** `MarkdownProcessor`, `PlainTextProcessor`, and `RichTextProcessor` implementations sharing the same contract.
28
+ - **Design Patterns:**
29
+ - **Facade Pattern:** `CloudNotesClient` acts as the single point of entry.
30
+ - **Strategy Pattern:** Interchangeable search algorithms (`search.py`).
31
+ - **Template Method Pattern:** Processors share generic steps while overriding specific details.
32
+ - **Singleton Pattern:** Global configuration management.
33
+
34
+ ## Installation
35
+ ```bash
36
+ pip install cloudnoteslib
37
+ ```
@@ -0,0 +1,18 @@
1
+ # cloudnoteslib
2
+
3
+ A comprehensive, reusable Python Object-Oriented Programming (OOP) library designed for text note processing, content analysis, security, and exports.
4
+
5
+ ## Features & OOP Principles
6
+ - **Encapsulation:** Strongly typed `Note` and `Tag` models with data validation via `@property`.
7
+ - **Abstraction:** Abstract `NoteProcessor` base class guaranteeing unified interfaces.
8
+ - **Inheritance & Polymorphism:** `MarkdownProcessor`, `PlainTextProcessor`, and `RichTextProcessor` implementations sharing the same contract.
9
+ - **Design Patterns:**
10
+ - **Facade Pattern:** `CloudNotesClient` acts as the single point of entry.
11
+ - **Strategy Pattern:** Interchangeable search algorithms (`search.py`).
12
+ - **Template Method Pattern:** Processors share generic steps while overriding specific details.
13
+ - **Singleton Pattern:** Global configuration management.
14
+
15
+ ## Installation
16
+ ```bash
17
+ pip install cloudnoteslib
18
+ ```
@@ -0,0 +1,128 @@
1
+ """
2
+ cloudnoteslib — A reusable Python OOP library for Note processing and analysis.
3
+
4
+ Features:
5
+ - Encapsulated Note and Tag models
6
+ - Polymorphic content processors (Markdown, Plain, Rich text)
7
+ - Strategy-pattern based Search Engine
8
+ - AES-256 Encryption & XSS Sanitization
9
+ - Export tools (JSON, Markdown)
10
+
11
+ Version: 0.1.0
12
+ """
13
+
14
+ __version__ = "0.1.0"
15
+ __author__ = "Kavya"
16
+ __email__ = "kavyavegunta27@gmail.com"
17
+
18
+ from typing import List, Optional
19
+
20
+ # Core Models
21
+ from .models.note import Note
22
+ from .models.tag import Tag
23
+ from .models.note_collection import NoteCollection
24
+
25
+ # Processors
26
+ from .processors.base import NoteProcessor
27
+ from .processors.markdown_processor import MarkdownProcessor
28
+ from .processors.plaintext_processor import PlainTextProcessor
29
+ from .processors.richtext_processor import RichTextProcessor
30
+
31
+ # Analyzers
32
+ from .analyzers.content_analyzer import ContentAnalyzer
33
+ from .analyzers.statistics import NoteStatistics
34
+ from .analyzers.search import SearchEngine
35
+
36
+ # Security & Exporters
37
+ from .security.encryptor import NoteEncryptor
38
+ from .security.sanitizer import ContentSanitizer
39
+ from .exporters.json_exporter import JSONExporter
40
+ from .exporters.markdown_exporter import MarkdownExporter
41
+
42
+ # Config & Exceptions
43
+ from .config import config, NoteConfig
44
+ from .exceptions import CloudNotesLibError, ProcessorNotSupportedError
45
+
46
+
47
+ # =========================================================================
48
+ # CloudNotesClient — FACADE PATTERN
49
+ # =========================================================================
50
+
51
+ class CloudNotesClient:
52
+ """
53
+ High-level Facade for cloudnoteslib.
54
+
55
+ Provides a simplified, unified interface hiding the complexity of
56
+ processors, analyzers, security, and exporters.
57
+ """
58
+
59
+ def __init__(self, processor_type: str = "markdown"):
60
+ self.processor_type = processor_type
61
+ self._processor = self._create_processor(processor_type)
62
+ self._analyzer = ContentAnalyzer()
63
+ self._search_engine = SearchEngine()
64
+ self._sanitizer = ContentSanitizer()
65
+ self._encryptor = NoteEncryptor(salt=config.password_salt)
66
+ self._stats = NoteStatistics()
67
+
68
+ def _create_processor(self, type_name: str) -> NoteProcessor:
69
+ """Factory Method for creating content processors."""
70
+ processors = {
71
+ "markdown": MarkdownProcessor(),
72
+ "plaintext": PlainTextProcessor(),
73
+ "richtext": RichTextProcessor(),
74
+ }
75
+ normalized = type_name.strip().lower()
76
+ if normalized not in processors:
77
+ raise ProcessorNotSupportedError(f"Processor '{type_name}' not supported.")
78
+ return processors[normalized]
79
+
80
+ def process_note(self, note: Note) -> Note:
81
+ """Sanitize and process note content."""
82
+ clean_content = self._sanitizer.sanitize(note.content)
83
+ processed_content = self._processor.process(clean_content)
84
+ # We don't overwrite the original content in the note with processed plain text,
85
+ # but we could. For now, just return a sanitized version.
86
+ note.content = clean_content
87
+ return note
88
+
89
+ def analyze_content(self, note: Note) -> dict:
90
+ """Return analytics for a single note."""
91
+ return self._analyzer.analyze(note)
92
+
93
+ def get_collection_stats(self, collection: NoteCollection) -> dict:
94
+ """Return collection-level statistics."""
95
+ return self._stats.get_summary(collection)
96
+
97
+ def search_notes(self, collection: NoteCollection, query: str, strategy: str = "exact") -> NoteCollection:
98
+ """Search notes using a specific strategy."""
99
+ self._search_engine.set_strategy(strategy)
100
+ return self._search_engine.search(collection, query)
101
+
102
+ def encrypt_content(self, plain_text: str, password: str) -> str:
103
+ """Encrypt content string."""
104
+ return self._encryptor.encrypt(plain_text, password)
105
+
106
+ def decrypt_content(self, encrypted_text: str, password: str) -> str:
107
+ """Decrypt content string."""
108
+ return self._encryptor.decrypt(encrypted_text, password)
109
+
110
+ def export(self, collection: NoteCollection, format: str = "json") -> str:
111
+ """Export collection to requested format."""
112
+ if format.lower() == "json":
113
+ return JSONExporter().export(collection)
114
+ elif format.lower() in ("md", "markdown"):
115
+ return MarkdownExporter().export(collection)
116
+ else:
117
+ raise ValueError(f"Export format not supported: {format}")
118
+
119
+
120
+ __all__ = [
121
+ "CloudNotesClient",
122
+ "Note", "Tag", "NoteCollection",
123
+ "NoteProcessor", "MarkdownProcessor", "PlainTextProcessor", "RichTextProcessor",
124
+ "ContentAnalyzer", "NoteStatistics", "SearchEngine",
125
+ "NoteEncryptor", "ContentSanitizer",
126
+ "JSONExporter", "MarkdownExporter",
127
+ "config", "CloudNotesLibError"
128
+ ]
@@ -0,0 +1,14 @@
1
+ """
2
+ cloudnoteslib.analyzers — Content Analysis and Search.
3
+
4
+ Exports:
5
+ ContentAnalyzer: Single-note content analysis.
6
+ NoteStatistics: Collection-level analytics.
7
+ SearchEngine: Multi-strategy search (Strategy Pattern).
8
+ """
9
+
10
+ from .content_analyzer import ContentAnalyzer
11
+ from .statistics import NoteStatistics
12
+ from .search import SearchEngine
13
+
14
+ __all__ = ["ContentAnalyzer", "NoteStatistics", "SearchEngine"]
@@ -0,0 +1,180 @@
1
+ """
2
+ cloudnoteslib.analyzers.content_analyzer — Single-Note Content Analysis.
3
+
4
+ Provides detailed analytics for individual notes including word frequency,
5
+ readability metrics, and content classification.
6
+
7
+ Example:
8
+ >>> analyzer = ContentAnalyzer()
9
+ >>> result = analyzer.analyze(note)
10
+ >>> result['word_count']
11
+ 150
12
+ >>> result['reading_time']
13
+ 0.8
14
+ >>> result['top_words']
15
+ [('project', 5), ('cloud', 3), ('deploy', 2)]
16
+ """
17
+
18
+ import re
19
+ from collections import Counter
20
+ from typing import List, Dict
21
+ from ..models.note import Note
22
+
23
+
24
+ class ContentAnalyzer:
25
+ """
26
+ Performs detailed content analysis on a single Note.
27
+
28
+ Extracts metrics such as word frequency, sentence statistics,
29
+ readability indicators, and content structure information.
30
+ """
31
+
32
+ # Common English stop words to exclude from frequency analysis
33
+ STOP_WORDS = frozenset({
34
+ "the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
35
+ "have", "has", "had", "do", "does", "did", "will", "would", "could",
36
+ "should", "may", "might", "shall", "can", "need", "must", "ought",
37
+ "i", "me", "my", "we", "our", "you", "your", "he", "she", "it",
38
+ "they", "them", "their", "this", "that", "these", "those", "what",
39
+ "which", "who", "whom", "and", "but", "or", "nor", "not", "so",
40
+ "if", "then", "than", "too", "very", "just", "about", "above",
41
+ "after", "again", "all", "also", "any", "because", "before",
42
+ "between", "both", "by", "each", "for", "from", "get", "got",
43
+ "how", "in", "into", "its", "more", "most", "no", "of", "off",
44
+ "on", "only", "other", "out", "own", "same", "some", "such",
45
+ "to", "up", "with", "when", "where", "while",
46
+ })
47
+
48
+ def analyze(self, note: Note) -> dict:
49
+ """
50
+ Perform comprehensive content analysis on a single note.
51
+
52
+ Args:
53
+ note: Note object to analyze.
54
+
55
+ Returns:
56
+ Dictionary containing all analysis metrics.
57
+ """
58
+ content = note.content
59
+ words = self._extract_words(content)
60
+
61
+ return {
62
+ "title": note.title,
63
+ "word_count": len(words),
64
+ "char_count": note.char_count,
65
+ "sentence_count": note.sentence_count,
66
+ "paragraph_count": self._count_paragraphs(content),
67
+ "reading_time": note.reading_time,
68
+ "avg_word_length": self._avg_word_length(words),
69
+ "avg_sentence_length": self._avg_sentence_length(content, words),
70
+ "top_words": self._top_words(words, n=10),
71
+ "unique_word_count": len(set(words)),
72
+ "vocabulary_richness": self._vocabulary_richness(words),
73
+ "has_links": self._has_links(content),
74
+ "has_code": self._has_code(content),
75
+ "tags": note.tags,
76
+ "is_pinned": note.is_pinned,
77
+ }
78
+
79
+ def get_word_frequency(self, note: Note, top_n: int = 20) -> List[tuple]:
80
+ """
81
+ Get the most frequently used meaningful words.
82
+
83
+ Excludes stop words and short words to focus on content-bearing terms.
84
+
85
+ Args:
86
+ note: Note to analyze.
87
+ top_n: Number of top words to return.
88
+
89
+ Returns:
90
+ List of (word, count) tuples, sorted by frequency.
91
+ """
92
+ words = self._extract_words(note.content)
93
+ return self._top_words(words, n=top_n)
94
+
95
+ def get_readability_score(self, note: Note) -> dict:
96
+ """
97
+ Calculate readability metrics for the note.
98
+
99
+ Uses a simplified Flesch-Kincaid-inspired scoring based on
100
+ average sentence length and average word length.
101
+
102
+ Args:
103
+ note: Note to analyze.
104
+
105
+ Returns:
106
+ Dictionary with readability metrics and a difficulty level.
107
+ """
108
+ words = self._extract_words(note.content)
109
+ if not words:
110
+ return {"score": 0, "level": "empty", "avg_sentence_len": 0}
111
+
112
+ avg_sentence_len = self._avg_sentence_length(note.content, words)
113
+ avg_word_len = self._avg_word_length(words)
114
+
115
+ # Simplified readability: lower score = easier to read
116
+ score = round((avg_sentence_len * 0.39) + (avg_word_len * 11.8) - 15.59, 1)
117
+
118
+ if score < 30:
119
+ level = "very_easy"
120
+ elif score < 50:
121
+ level = "easy"
122
+ elif score < 60:
123
+ level = "moderate"
124
+ elif score < 70:
125
+ level = "somewhat_difficult"
126
+ else:
127
+ level = "difficult"
128
+
129
+ return {
130
+ "score": max(0, score),
131
+ "level": level,
132
+ "avg_sentence_length": avg_sentence_len,
133
+ "avg_word_length": avg_word_len,
134
+ }
135
+
136
+ # ─── Private Helper Methods ───
137
+
138
+ def _extract_words(self, content: str) -> List[str]:
139
+ """Extract individual words from content (lowercase, alphanumeric only)."""
140
+ return re.findall(r'[a-zA-Z]+', content.lower())
141
+
142
+ def _count_paragraphs(self, content: str) -> int:
143
+ """Count paragraphs (blocks separated by blank lines)."""
144
+ if not content.strip():
145
+ return 0
146
+ paragraphs = re.split(r'\n\s*\n', content.strip())
147
+ return len([p for p in paragraphs if p.strip()])
148
+
149
+ def _avg_word_length(self, words: List[str]) -> float:
150
+ """Average character length of words."""
151
+ if not words:
152
+ return 0.0
153
+ return round(sum(len(w) for w in words) / len(words), 1)
154
+
155
+ def _avg_sentence_length(self, content: str, words: List[str]) -> float:
156
+ """Average number of words per sentence."""
157
+ sentences = re.split(r'[.!?]+', content.strip())
158
+ sentence_count = len([s for s in sentences if s.strip()])
159
+ if sentence_count == 0:
160
+ return 0.0
161
+ return round(len(words) / sentence_count, 1)
162
+
163
+ def _top_words(self, words: List[str], n: int = 10) -> List[tuple]:
164
+ """Get top N most frequent non-stop-words."""
165
+ meaningful = [w for w in words if w not in self.STOP_WORDS and len(w) > 2]
166
+ return Counter(meaningful).most_common(n)
167
+
168
+ def _vocabulary_richness(self, words: List[str]) -> float:
169
+ """Ratio of unique words to total words (type-token ratio)."""
170
+ if not words:
171
+ return 0.0
172
+ return round(len(set(words)) / len(words), 3)
173
+
174
+ def _has_links(self, content: str) -> bool:
175
+ """Check if content contains URLs."""
176
+ return bool(re.search(r'https?://\S+', content))
177
+
178
+ def _has_code(self, content: str) -> bool:
179
+ """Check if content contains code blocks or inline code."""
180
+ return bool(re.search(r'```|`[^`]+`', content))
@@ -0,0 +1,143 @@
1
+ """
2
+ cloudnoteslib.analyzers.search — Search Engine (Strategy Pattern).
3
+
4
+ Demonstrates the STRATEGY PATTERN:
5
+ SearchEngine uses a configured strategy (Exact, Fuzzy, or Regex)
6
+ to perform searches. The algorithm can be interchanged at runtime
7
+ without modifying the NoteCollection or the SearchEngine itself.
8
+
9
+ Example:
10
+ >>> engine = SearchEngine(strategy="fuzzy")
11
+ >>> results = engine.search(collection, "prjct")
12
+ """
13
+
14
+ import re
15
+ from abc import ABC, abstractmethod
16
+ from typing import List
17
+ from ..models.note import Note
18
+ from ..models.note_collection import NoteCollection
19
+
20
+
21
+ # ─── Strategy Interface ───
22
+
23
+ class SearchStrategy(ABC):
24
+ """Abstract interface for search algorithms."""
25
+
26
+ @abstractmethod
27
+ def search(self, notes: List[Note], query: str) -> List[Note]:
28
+ pass
29
+
30
+
31
+ # ─── Concrete Strategies ───
32
+
33
+ class ExactMatchStrategy(SearchStrategy):
34
+ """Simple case-insensitive substring match."""
35
+
36
+ def search(self, notes: List[Note], query: str) -> List[Note]:
37
+ q = query.strip().lower()
38
+ if not q:
39
+ return notes.copy()
40
+
41
+ results = []
42
+ for note in notes:
43
+ if q in note.title.lower() or q in note.content.lower():
44
+ results.append(note)
45
+ return results
46
+
47
+
48
+ class RegexStrategy(SearchStrategy):
49
+ """Advanced search using regular expressions."""
50
+
51
+ def search(self, notes: List[Note], pattern: str) -> List[Note]:
52
+ if not pattern.strip():
53
+ return notes.copy()
54
+
55
+ try:
56
+ regex = re.compile(pattern, re.IGNORECASE)
57
+ except re.error:
58
+ # Fallback to exact match if regex is invalid
59
+ return ExactMatchStrategy().search(notes, pattern)
60
+
61
+ results = []
62
+ for note in notes:
63
+ if regex.search(note.title) or regex.search(note.content):
64
+ results.append(note)
65
+ return results
66
+
67
+
68
+ class FuzzyStrategy(SearchStrategy):
69
+ """
70
+ Very basic fuzzy search.
71
+ Matches if all characters in query appear in the text in order.
72
+ (e.g., 'prt' matches 'project').
73
+ """
74
+
75
+ def search(self, notes: List[Note], query: str) -> List[Note]:
76
+ q = query.strip().lower()
77
+ if not q:
78
+ return notes.copy()
79
+
80
+ # Build regex pattern: p.*r.*t
81
+ escaped_chars = [re.escape(c) for c in q]
82
+ pattern = '.*'.join(escaped_chars)
83
+
84
+ try:
85
+ regex = re.compile(pattern, re.IGNORECASE)
86
+ except re.error:
87
+ return ExactMatchStrategy().search(notes, query)
88
+
89
+ results = []
90
+ for note in notes:
91
+ if regex.search(note.title) or regex.search(note.content):
92
+ results.append(note)
93
+ return results
94
+
95
+
96
+ # ─── Context Class ───
97
+
98
+ class SearchEngine:
99
+ """
100
+ Context class that executes searches using the configured Strategy.
101
+ """
102
+
103
+ STRATEGIES = {
104
+ "exact": ExactMatchStrategy(),
105
+ "regex": RegexStrategy(),
106
+ "fuzzy": FuzzyStrategy(),
107
+ }
108
+
109
+ def __init__(self, strategy: str = "exact"):
110
+ """
111
+ Initialize with a specific search strategy.
112
+
113
+ Args:
114
+ strategy: 'exact', 'regex', or 'fuzzy'.
115
+ """
116
+ self.set_strategy(strategy)
117
+
118
+ def set_strategy(self, strategy_name: str) -> None:
119
+ """Change internal strategy at runtime."""
120
+ normalized = strategy_name.strip().lower()
121
+ if normalized not in self.STRATEGIES:
122
+ raise ValueError(f"Unknown strategy: {strategy_name}. Choose from: {list(self.STRATEGIES.keys())}")
123
+ self._strategy = self.STRATEGIES[normalized]
124
+ self._current_strategy_name = normalized
125
+
126
+ @property
127
+ def strategy_name(self) -> str:
128
+ return self._current_strategy_name
129
+
130
+ def search(self, collection: NoteCollection, query: str) -> NoteCollection:
131
+ """
132
+ Execute search on a collection.
133
+
134
+ Args:
135
+ collection: The NoteCollection to search.
136
+ query: The search string.
137
+
138
+ Returns:
139
+ A new NoteCollection with the results.
140
+ """
141
+ notes_list = list(collection)
142
+ results = self._strategy.search(notes_list, query)
143
+ return NoteCollection(results)
@@ -0,0 +1,88 @@
1
+ """
2
+ cloudnoteslib.analyzers.statistics — Collection Statistics Analyzer.
3
+
4
+ Analyzes a NoteCollection to produce aggregate statistics across
5
+ multiple notes.
6
+
7
+ Example:
8
+ >>> stats = NoteStatistics()
9
+ >>> summary = stats.get_summary(collection)
10
+ >>> summary['total_notes']
11
+ 42
12
+ >>> len(summary['popular_tags'])
13
+ 5
14
+ """
15
+
16
+ from typing import List, Dict
17
+ from ..models.note_collection import NoteCollection
18
+
19
+
20
+ class NoteStatistics:
21
+ """
22
+ Computes aggregate analytics over a NoteCollection.
23
+ """
24
+
25
+ def get_summary(self, collection: NoteCollection) -> dict:
26
+ """
27
+ Produce a comprehensive statistical summary of the collection.
28
+
29
+ Args:
30
+ collection: NoteCollection to analyze.
31
+
32
+ Returns:
33
+ Dictionary with collection-level metrics.
34
+ """
35
+ if len(collection) == 0:
36
+ return self._empty_summary()
37
+
38
+ return {
39
+ "total_notes": len(collection),
40
+ "total_words": collection.total_words,
41
+ "total_reading_time": collection.total_reading_time,
42
+ "average_word_count": collection.average_word_count,
43
+
44
+ "pinned_count": len(collection.get_pinned()),
45
+ "archived_count": len(collection.get_archived()),
46
+ "active_count": len(collection.get_active()),
47
+
48
+ "total_tags": len(collection.all_tags),
49
+ "popular_tags": self._get_top_tags(collection, count=5),
50
+
51
+ "longest_note": self._note_summary(collection.longest_note),
52
+ "shortest_note": self._note_summary(collection.shortest_note),
53
+ "most_recent": self._note_summary(collection.most_recent),
54
+ }
55
+
56
+ def _get_top_tags(self, collection: NoteCollection, count: int = 5) -> List[dict]:
57
+ """Get the most frequently used tags with their counts."""
58
+ tag_counts = collection.tag_counts
59
+ top = list(tag_counts.items())[:count]
60
+ return [{"tag": t[0], "count": t[1]} for t in top]
61
+
62
+ def _note_summary(self, note) -> dict:
63
+ """Return a brief summary of a single note for stats."""
64
+ if not note:
65
+ return {}
66
+ return {
67
+ "id": note.note_id,
68
+ "title": note.title,
69
+ "word_count": note.word_count,
70
+ "created_at": note.created_at.isoformat() if note.created_at else None,
71
+ }
72
+
73
+ def _empty_summary(self) -> dict:
74
+ """Return zeroed summary for empty collections."""
75
+ return {
76
+ "total_notes": 0,
77
+ "total_words": 0,
78
+ "total_reading_time": 0.0,
79
+ "average_word_count": 0.0,
80
+ "pinned_count": 0,
81
+ "archived_count": 0,
82
+ "active_count": 0,
83
+ "total_tags": 0,
84
+ "popular_tags": [],
85
+ "longest_note": {},
86
+ "shortest_note": {},
87
+ "most_recent": {},
88
+ }
@@ -0,0 +1,28 @@
1
+ """
2
+ cloudnoteslib.config — Note Configuration Singleton.
3
+
4
+ Provides a global configuration point for the library.
5
+ Demonstrates the SINGLETON pattern.
6
+ """
7
+
8
+ class NoteConfig:
9
+ """
10
+ Singleton configuration manager for cloudnoteslib.
11
+ """
12
+
13
+ _instance = None
14
+
15
+ def __new__(cls):
16
+ if cls._instance is None:
17
+ cls._instance = super(NoteConfig, cls).__new__(cls)
18
+ cls._instance._init_defaults()
19
+ return cls._instance
20
+
21
+ def _init_defaults(self):
22
+ self.default_processor = "markdown"
23
+ self.max_note_size_mb = 5
24
+ self.default_search_strategy = "exact"
25
+ self.password_salt = b"cloudnotes_salt_123"
26
+
27
+ # Create the singleton instance
28
+ config = NoteConfig()
@@ -0,0 +1,19 @@
1
+ """
2
+ cloudnoteslib.exceptions — Custom Exceptions.
3
+ """
4
+
5
+ class CloudNotesLibError(Exception):
6
+ """Base exception for cloudnoteslib."""
7
+ pass
8
+
9
+ class NoteValidationError(CloudNotesLibError):
10
+ """Raised when note validation fails (e.g. title too long)."""
11
+ pass
12
+
13
+ class ProcessorNotSupportedError(CloudNotesLibError):
14
+ """Raised when an unknown processor type is requested."""
15
+ pass
16
+
17
+ class SecurityError(CloudNotesLibError):
18
+ """Raised for encryption/decryption or authorization failures."""
19
+ pass
@@ -0,0 +1,11 @@
1
+ """
2
+ cloudnoteslib.exporters — Note Exporters.
3
+
4
+ Provides mechanisms to export note collections into various formats.
5
+ """
6
+
7
+ from .base import NoteExporter
8
+ from .json_exporter import JSONExporter
9
+ from .markdown_exporter import MarkdownExporter
10
+
11
+ __all__ = ["NoteExporter", "JSONExporter", "MarkdownExporter"]