3
3
from __future__ import annotations
4
4
5
5
import logging
6
+ import sys
6
7
import typing as t
7
8
8
9
from singer_sdk .exceptions import InvalidStreamSortException
9
10
from singer_sdk .helpers ._typing import to_json_compatible
10
11
12
+ if sys .version_info < (3 , 10 ):
13
+ from typing_extensions import TypeAlias
14
+ else :
15
+ from typing import TypeAlias # noqa: ICN003
16
+
11
17
if t .TYPE_CHECKING :
12
18
import datetime
13
19
21
27
STARTING_MARKER = "starting_replication_value"
22
28
23
29
logger = logging .getLogger ("singer_sdk" )
30
+ StreamStateDict : TypeAlias = t .Dict [str , t .Any ]
31
+
32
+
33
+ class PartitionsStateDict (t .TypedDict , total = False ):
34
+ partitions : list [StreamStateDict ]
35
+
36
+
37
+ class TapStateDict (t .TypedDict , total = False ):
38
+ """State dictionary type."""
39
+
40
+ bookmarks : dict [str , StreamStateDict | PartitionsStateDict ]
24
41
25
42
26
43
def get_state_if_exists (
27
- tap_state : dict ,
44
+ tap_state : TapStateDict ,
28
45
tap_stream_id : str ,
29
- state_partition_context : dict | None = None ,
46
+ state_partition_context : dict [ str , t . Any ] | None = None ,
30
47
key : str | None = None ,
31
- ) -> t . Any | None : # noqa: ANN401
48
+ ) -> StreamStateDict | None :
32
49
"""Return the stream or partition state, creating a new one if it does not exist.
33
50
34
51
Args:
@@ -46,34 +63,49 @@ def get_state_if_exists(
46
63
ValueError: Raised if state is invalid or cannot be parsed.
47
64
"""
48
65
if "bookmarks" not in tap_state :
66
+ # Not a valid state, e.g. {}
49
67
return None
68
+
50
69
if tap_stream_id not in tap_state ["bookmarks" ]:
70
+ # Stream not present in state, e.g. {"bookmarks": {}}
51
71
return None
52
72
73
+ # At this point state looks like {"bookmarks": {"my_stream": {"key": "value""}}}
74
+
75
+ # stream_state: {"key": "value", "partitions"?: ...}
53
76
stream_state = tap_state ["bookmarks" ][tap_stream_id ]
54
77
if not state_partition_context :
55
- return stream_state .get (key , None ) if key else stream_state
78
+ # Either 'value' if key is specified, or {}
79
+ return stream_state .get (key , None ) if key else stream_state # type: ignore[return-value]
80
+
56
81
if "partitions" not in stream_state :
57
82
return None # No partitions defined
58
83
84
+ # stream_state: {"partitions": [{"context": {"key": "value"}}]} # noqa: ERA001
85
+
59
86
matched_partition = _find_in_partitions_list (
60
87
stream_state ["partitions" ],
61
88
state_partition_context ,
62
89
)
90
+
63
91
if matched_partition is None :
64
92
return None # Partition definition not present
93
+
65
94
return matched_partition .get (key , None ) if key else matched_partition
66
95
67
96
68
- def get_state_partitions_list (tap_state : dict , tap_stream_id : str ) -> list [dict ] | None :
97
+ def get_state_partitions_list (
98
+ tap_state : TapStateDict ,
99
+ tap_stream_id : str ,
100
+ ) -> list [StreamStateDict ] | None :
69
101
"""Return a list of partitions defined in the state, or None if not defined."""
70
102
return (get_state_if_exists (tap_state , tap_stream_id ) or {}).get ("partitions" , None ) # type: ignore[no-any-return]
71
103
72
104
73
105
def _find_in_partitions_list (
74
- partitions : list [dict ],
106
+ partitions : list [StreamStateDict ],
75
107
state_partition_context : types .Context ,
76
- ) -> dict | None :
108
+ ) -> StreamStateDict | None :
77
109
found = [
78
110
partition_state
79
111
for partition_state in partitions
@@ -99,10 +131,10 @@ def _create_in_partitions_list(
99
131
100
132
101
133
def get_writeable_state_dict (
102
- tap_state : dict ,
134
+ tap_state : TapStateDict ,
103
135
tap_stream_id : str ,
104
136
state_partition_context : types .Context | None = None ,
105
- ) -> dict :
137
+ ) -> StreamStateDict :
106
138
"""Return the stream or partition state, creating a new one if it does not exist.
107
139
108
140
Args:
@@ -125,13 +157,13 @@ def get_writeable_state_dict(
125
157
tap_state ["bookmarks" ] = {}
126
158
if tap_stream_id not in tap_state ["bookmarks" ]:
127
159
tap_state ["bookmarks" ][tap_stream_id ] = {}
128
- stream_state = t . cast ( dict , tap_state ["bookmarks" ][tap_stream_id ])
160
+ stream_state = tap_state ["bookmarks" ][tap_stream_id ]
129
161
if not state_partition_context :
130
- return stream_state
162
+ return stream_state # type: ignore[return-value]
131
163
132
164
if "partitions" not in stream_state :
133
165
stream_state ["partitions" ] = []
134
- stream_state_partitions : list [dict ] = stream_state ["partitions" ]
166
+ stream_state_partitions : list [StreamStateDict ] = stream_state ["partitions" ]
135
167
if found := _find_in_partitions_list (
136
168
stream_state_partitions ,
137
169
state_partition_context ,
@@ -142,7 +174,7 @@ def get_writeable_state_dict(
142
174
143
175
144
176
def write_stream_state (
145
- tap_state : dict ,
177
+ tap_state : TapStateDict ,
146
178
tap_stream_id : str ,
147
179
key : str ,
148
180
val : t .Any , # noqa: ANN401
@@ -158,12 +190,14 @@ def write_stream_state(
158
190
state_dict [key ] = val
159
191
160
192
161
- def reset_state_progress_markers (stream_or_partition_state : dict ) -> dict | None :
193
+ def reset_state_progress_markers (
194
+ stream_or_partition_state : StreamStateDict | PartitionsStateDict ,
195
+ ) -> dict | None :
162
196
"""Wipe the state once sync is complete.
163
197
164
198
For logging purposes, return the wiped 'progress_markers' object if it existed.
165
199
"""
166
- progress_markers = stream_or_partition_state .pop (PROGRESS_MARKERS , {})
200
+ progress_markers = stream_or_partition_state .pop (PROGRESS_MARKERS , {}) # type: ignore[misc]
167
201
# Remove auto-generated human-readable note:
168
202
progress_markers .pop (PROGRESS_MARKER_NOTE , None )
169
203
# Return remaining 'progress_markers' if any:
0 commit comments