notesctl 1.0.0__tar.gz → 1.0.1__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.
- {notesctl-1.0.0 → notesctl-1.0.1}/CHANGELOG.md +10 -1
- {notesctl-1.0.0 → notesctl-1.0.1}/PKG-INFO +1 -1
- {notesctl-1.0.0 → notesctl-1.0.1}/pyproject.toml +1 -1
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/__init__.py +1 -1
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/database/connection.py +19 -8
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/database/queries.py +7 -2
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/parser/note_parser.py +6 -3
- {notesctl-1.0.0 → notesctl-1.0.1}/uv.lock +1 -1
- {notesctl-1.0.0 → notesctl-1.0.1}/.github/workflows/ci.yml +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/.github/workflows/publish.yml +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/.gitignore +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/LICENSE +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/README.md +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/proto/notestore.proto +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/cli.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/converter/__init__.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/converter/markdown.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/database/__init__.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/database/models.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/exporter.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/parser/__init__.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/parser/protobuf/__init__.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/src/notesctl/parser/protobuf/notestore_pb2.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/tests/__init__.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/tests/test_connection.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/tests/test_converter.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/tests/test_models.py +0 -0
- {notesctl-1.0.0 → notesctl-1.0.1}/tests/test_parser.py +0 -0
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.1] - 2026-01-17
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed mypy strict type checking errors across database and parser modules
|
|
15
|
+
- Added proper type annotations for tuple parameters and return types
|
|
16
|
+
- Fixed type-checking imports to satisfy ruff TC rules
|
|
17
|
+
|
|
10
18
|
## [1.0.0] - 2026-01-17
|
|
11
19
|
|
|
12
20
|
### Added
|
|
@@ -34,5 +42,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
34
42
|
- Original Notes database is never modified
|
|
35
43
|
- All queries validated to be SELECT statements only
|
|
36
44
|
|
|
37
|
-
[Unreleased]: https://github.com/jwmoss/notesctl/compare/v1.0.
|
|
45
|
+
[Unreleased]: https://github.com/jwmoss/notesctl/compare/v1.0.1...HEAD
|
|
46
|
+
[1.0.1]: https://github.com/jwmoss/notesctl/compare/v1.0.0...v1.0.1
|
|
38
47
|
[1.0.0]: https://github.com/jwmoss/notesctl/releases/tag/v1.0.0
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"""Safe read-only database connection for Apple Notes."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import shutil
|
|
4
6
|
import sqlite3
|
|
5
7
|
import tempfile
|
|
6
|
-
from collections.abc import Iterator
|
|
7
8
|
from contextlib import contextmanager
|
|
8
9
|
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
9
11
|
|
|
10
12
|
from rich.console import Console
|
|
11
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Iterator
|
|
16
|
+
|
|
12
17
|
console = Console()
|
|
13
18
|
|
|
14
19
|
# Default location of the Apple Notes database
|
|
@@ -100,7 +105,7 @@ class SafeNotesDatabase:
|
|
|
100
105
|
raise RuntimeError("Database not connected. Call connect() first.")
|
|
101
106
|
return self._connection
|
|
102
107
|
|
|
103
|
-
def execute(self, query: str, params: tuple = ()) -> sqlite3.Cursor:
|
|
108
|
+
def execute(self, query: str, params: tuple[Any, ...] = ()) -> sqlite3.Cursor:
|
|
104
109
|
"""
|
|
105
110
|
Execute a SELECT query safely.
|
|
106
111
|
|
|
@@ -121,20 +126,26 @@ class SafeNotesDatabase:
|
|
|
121
126
|
|
|
122
127
|
return self.connection.execute(query, params)
|
|
123
128
|
|
|
124
|
-
def fetchall(self, query: str, params: tuple = ()) -> list[sqlite3.Row]:
|
|
129
|
+
def fetchall(self, query: str, params: tuple[Any, ...] = ()) -> list[sqlite3.Row]:
|
|
125
130
|
"""Execute a query and fetch all results."""
|
|
126
|
-
return self.execute(query, params).fetchall()
|
|
131
|
+
return list(self.execute(query, params).fetchall())
|
|
127
132
|
|
|
128
|
-
def fetchone(self, query: str, params: tuple = ()) -> sqlite3.Row | None:
|
|
133
|
+
def fetchone(self, query: str, params: tuple[Any, ...] = ()) -> sqlite3.Row | None:
|
|
129
134
|
"""Execute a query and fetch one result."""
|
|
130
|
-
|
|
135
|
+
result: sqlite3.Row | None = self.execute(query, params).fetchone()
|
|
136
|
+
return result
|
|
131
137
|
|
|
132
|
-
def __enter__(self) ->
|
|
138
|
+
def __enter__(self) -> SafeNotesDatabase:
|
|
133
139
|
"""Context manager entry."""
|
|
134
140
|
self.connect()
|
|
135
141
|
return self
|
|
136
142
|
|
|
137
|
-
def __exit__(
|
|
143
|
+
def __exit__(
|
|
144
|
+
self,
|
|
145
|
+
exc_type: type[BaseException] | None,
|
|
146
|
+
exc_val: BaseException | None,
|
|
147
|
+
exc_tb: Any,
|
|
148
|
+
) -> None:
|
|
138
149
|
"""Context manager exit."""
|
|
139
150
|
self.close()
|
|
140
151
|
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"""SQL queries for reading Apple Notes data (SELECT only)."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from datetime import UTC, datetime
|
|
4
6
|
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
5
8
|
|
|
6
|
-
from .connection import SafeNotesDatabase
|
|
7
9
|
from .models import Attachment, Folder, Note
|
|
8
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .connection import SafeNotesDatabase
|
|
13
|
+
|
|
9
14
|
# Apple's Core Data timestamp epoch (2001-01-01 00:00:00 UTC)
|
|
10
15
|
APPLE_EPOCH = datetime(2001, 1, 1, tzinfo=UTC)
|
|
11
16
|
|
|
@@ -114,7 +119,7 @@ class NotesQueries:
|
|
|
114
119
|
WHERE n.ZTITLE1 IS NOT NULL
|
|
115
120
|
"""
|
|
116
121
|
|
|
117
|
-
params: list = []
|
|
122
|
+
params: list[Any] = []
|
|
118
123
|
|
|
119
124
|
if folder_id is not None:
|
|
120
125
|
query += " AND n.ZFOLDER = ?"
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Parser for Apple Notes gzip-compressed protobuf content."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import gzip
|
|
4
6
|
from dataclasses import dataclass, field
|
|
5
7
|
from enum import IntEnum
|
|
8
|
+
from typing import Any
|
|
6
9
|
|
|
7
|
-
from .protobuf.notestore_pb2 import NoteStoreProto
|
|
10
|
+
from .protobuf.notestore_pb2 import NoteStoreProto # type: ignore[attr-defined]
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
class StyleType(IntEnum):
|
|
@@ -69,7 +72,7 @@ class ParsedNote:
|
|
|
69
72
|
class NoteParser:
|
|
70
73
|
"""Parser for Apple Notes protobuf content."""
|
|
71
74
|
|
|
72
|
-
def __init__(self):
|
|
75
|
+
def __init__(self) -> None:
|
|
73
76
|
"""Initialize the parser."""
|
|
74
77
|
self._numbered_list_counters: dict[int, int] = {}
|
|
75
78
|
|
|
@@ -104,7 +107,7 @@ class NoteParser:
|
|
|
104
107
|
|
|
105
108
|
return self._parse_proto(proto, title)
|
|
106
109
|
|
|
107
|
-
def _parse_proto(self, proto:
|
|
110
|
+
def _parse_proto(self, proto: Any, title: str) -> ParsedNote:
|
|
108
111
|
"""Parse the protobuf message into structured content."""
|
|
109
112
|
note = ParsedNote(title=title)
|
|
110
113
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|