airbyte-cdk 6.26.0.dev4106__py3-none-any.whl → 6.26.0.dev4108__py3-none-any.whl

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 (51) hide show
  1. airbyte_cdk/cli/source_declarative_manifest/_run.py +3 -3
  2. airbyte_cdk/connector_builder/connector_builder_handler.py +2 -2
  3. airbyte_cdk/sources/declarative/async_job/job_orchestrator.py +7 -7
  4. airbyte_cdk/sources/declarative/auth/jwt.py +17 -11
  5. airbyte_cdk/sources/declarative/auth/oauth.py +22 -13
  6. airbyte_cdk/sources/declarative/auth/token.py +3 -8
  7. airbyte_cdk/sources/declarative/auth/token_provider.py +4 -5
  8. airbyte_cdk/sources/declarative/checks/check_dynamic_stream.py +19 -9
  9. airbyte_cdk/sources/declarative/concurrent_declarative_source.py +71 -34
  10. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +33 -4
  11. airbyte_cdk/sources/declarative/declarative_stream.py +3 -1
  12. airbyte_cdk/sources/declarative/incremental/concurrent_partition_cursor.py +93 -27
  13. airbyte_cdk/sources/declarative/incremental/datetime_based_cursor.py +7 -6
  14. airbyte_cdk/sources/declarative/manifest_declarative_source.py +5 -3
  15. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +22 -5
  16. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +138 -38
  17. airbyte_cdk/sources/declarative/partition_routers/async_job_partition_router.py +5 -5
  18. airbyte_cdk/sources/declarative/partition_routers/list_partition_router.py +4 -2
  19. airbyte_cdk/sources/declarative/partition_routers/substream_partition_router.py +49 -25
  20. airbyte_cdk/sources/declarative/requesters/error_handlers/http_response_filter.py +4 -4
  21. airbyte_cdk/sources/declarative/requesters/http_requester.py +5 -1
  22. airbyte_cdk/sources/declarative/requesters/paginators/default_paginator.py +6 -5
  23. airbyte_cdk/sources/declarative/requesters/request_option.py +83 -4
  24. airbyte_cdk/sources/declarative/requesters/request_options/datetime_based_request_options_provider.py +7 -6
  25. airbyte_cdk/sources/declarative/retrievers/async_retriever.py +6 -12
  26. airbyte_cdk/sources/declarative/retrievers/simple_retriever.py +4 -1
  27. airbyte_cdk/sources/declarative/schema/__init__.py +2 -0
  28. airbyte_cdk/sources/declarative/schema/dynamic_schema_loader.py +44 -5
  29. airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py +18 -11
  30. airbyte_cdk/sources/file_based/config/validate_config_transfer_modes.py +51 -0
  31. airbyte_cdk/sources/file_based/file_based_source.py +16 -55
  32. airbyte_cdk/sources/file_based/file_based_stream_reader.py +19 -31
  33. airbyte_cdk/sources/file_based/stream/default_file_based_stream.py +7 -7
  34. airbyte_cdk/sources/file_based/stream/identities_stream.py +5 -2
  35. airbyte_cdk/sources/streams/concurrent/state_converters/datetime_stream_state_converter.py +22 -13
  36. airbyte_cdk/sources/streams/core.py +6 -6
  37. airbyte_cdk/sources/streams/http/http.py +1 -2
  38. airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py +231 -62
  39. airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py +166 -83
  40. airbyte_cdk/sources/types.py +4 -2
  41. airbyte_cdk/sources/utils/transform.py +23 -2
  42. airbyte_cdk/utils/datetime_helpers.py +499 -0
  43. airbyte_cdk/utils/mapping_helpers.py +86 -27
  44. airbyte_cdk/utils/slice_hasher.py +8 -1
  45. airbyte_cdk-6.26.0.dev4108.dist-info/LICENSE_SHORT +1 -0
  46. {airbyte_cdk-6.26.0.dev4106.dist-info → airbyte_cdk-6.26.0.dev4108.dist-info}/METADATA +5 -5
  47. {airbyte_cdk-6.26.0.dev4106.dist-info → airbyte_cdk-6.26.0.dev4108.dist-info}/RECORD +50 -48
  48. {airbyte_cdk-6.26.0.dev4106.dist-info → airbyte_cdk-6.26.0.dev4108.dist-info}/WHEEL +1 -1
  49. airbyte_cdk/sources/file_based/config/permissions.py +0 -34
  50. {airbyte_cdk-6.26.0.dev4106.dist-info → airbyte_cdk-6.26.0.dev4108.dist-info}/LICENSE.txt +0 -0
  51. {airbyte_cdk-6.26.0.dev4106.dist-info → airbyte_cdk-6.26.0.dev4108.dist-info}/entry_points.txt +0 -0
@@ -33,6 +33,12 @@ from airbyte_cdk.sources.file_based.config.file_based_stream_config import (
33
33
  FileBasedStreamConfig,
34
34
  ValidationPolicy,
35
35
  )
36
+ from airbyte_cdk.sources.file_based.config.validate_config_transfer_modes import (
37
+ include_identities_stream,
38
+ preserve_directory_structure,
39
+ use_file_transfer,
40
+ use_permissions_transfer,
41
+ )
36
42
  from airbyte_cdk.sources.file_based.discovery_policy import (
37
43
  AbstractDiscoveryPolicy,
38
44
  DefaultDiscoveryPolicy,
@@ -164,7 +170,11 @@ class FileBasedSource(ConcurrentSourceAdapter, ABC):
164
170
  tracebacks = []
165
171
  for stream in streams:
166
172
  if isinstance(stream, IdentitiesStream):
167
- # Probably need to check identities endpoint/api access but will skip for now.
173
+ identity = next(iter(stream.load_identity_groups()))
174
+ if not identity:
175
+ errors.append(
176
+ "Unable to get identities for current configuration, please check your credentials"
177
+ )
168
178
  continue
169
179
  if not isinstance(stream, AbstractFileBasedStream):
170
180
  raise ValueError(f"Stream {stream} is not a file-based stream.")
@@ -172,8 +182,7 @@ class FileBasedSource(ConcurrentSourceAdapter, ABC):
172
182
  parsed_config = self._get_parsed_config(config)
173
183
  availability_method = (
174
184
  stream.availability_strategy.check_availability
175
- if self._use_file_transfer(parsed_config)
176
- or self._sync_acl_permissions(parsed_config)
185
+ if use_file_transfer(parsed_config) or use_permissions_transfer(parsed_config)
177
186
  else stream.availability_strategy.check_availability_and_parsability
178
187
  )
179
188
  (
@@ -300,7 +309,7 @@ class FileBasedSource(ConcurrentSourceAdapter, ABC):
300
309
 
301
310
  streams.append(stream)
302
311
 
303
- if self._sync_acl_permissions(parsed_config):
312
+ if include_identities_stream(parsed_config):
304
313
  identities_stream = self._make_identities_stream()
305
314
  streams.append(identities_stream)
306
315
  return streams
@@ -324,9 +333,9 @@ class FileBasedSource(ConcurrentSourceAdapter, ABC):
324
333
  validation_policy=self._validate_and_get_validation_policy(stream_config),
325
334
  errors_collector=self.errors_collector,
326
335
  cursor=cursor,
327
- use_file_transfer=self._use_file_transfer(parsed_config),
328
- preserve_directory_structure=self._preserve_directory_structure(parsed_config),
329
- sync_acl_permissions=self._sync_acl_permissions(parsed_config),
336
+ use_file_transfer=use_file_transfer(parsed_config),
337
+ preserve_directory_structure=preserve_directory_structure(parsed_config),
338
+ use_permissions_transfer=use_permissions_transfer(parsed_config),
330
339
  )
331
340
 
332
341
  def _make_identities_stream(
@@ -403,51 +412,3 @@ class FileBasedSource(ConcurrentSourceAdapter, ABC):
403
412
  "`input_schema` and `schemaless` options cannot both be set",
404
413
  model=FileBasedStreamConfig,
405
414
  )
406
-
407
- @staticmethod
408
- def _use_file_transfer(parsed_config: AbstractFileBasedSpec) -> bool:
409
- use_file_transfer = (
410
- hasattr(parsed_config.delivery_method, "delivery_type")
411
- and parsed_config.delivery_method.delivery_type == "use_file_transfer"
412
- )
413
- return use_file_transfer
414
-
415
- @staticmethod
416
- def _use_records_transfer(parsed_config: AbstractFileBasedSpec) -> bool:
417
- use_records_transfer = (
418
- hasattr(parsed_config.delivery_method, "delivery_type")
419
- and parsed_config.delivery_method.delivery_type == "use_records_transfer"
420
- )
421
- return use_records_transfer
422
-
423
- @staticmethod
424
- def _preserve_directory_structure(parsed_config: AbstractFileBasedSpec) -> bool:
425
- """
426
- Determines whether to preserve directory structure during file transfer.
427
-
428
- When enabled, files maintain their subdirectory paths in the destination.
429
- When disabled, files are flattened to the root of the destination.
430
-
431
- Args:
432
- parsed_config: The parsed configuration containing delivery method settings
433
-
434
- Returns:
435
- True if directory structure should be preserved (default), False otherwise
436
- """
437
- if (
438
- FileBasedSource._use_file_transfer(parsed_config)
439
- and hasattr(parsed_config.delivery_method, "preserve_directory_structure")
440
- and parsed_config.delivery_method.preserve_directory_structure is not None
441
- ):
442
- return parsed_config.delivery_method.preserve_directory_structure
443
- return True
444
-
445
- @staticmethod
446
- def _sync_acl_permissions(parsed_config: AbstractFileBasedSpec) -> bool:
447
- if (
448
- FileBasedSource._use_records_transfer(parsed_config)
449
- and hasattr(parsed_config.delivery_method, "sync_acl_permissions")
450
- and parsed_config.delivery_method.sync_acl_permissions is not None
451
- ):
452
- return parsed_config.delivery_method.sync_acl_permissions
453
- return False
@@ -13,6 +13,11 @@ from typing import Any, Dict, Iterable, List, Optional, Set
13
13
  from wcmatch.glob import GLOBSTAR, globmatch
14
14
 
15
15
  from airbyte_cdk.sources.file_based.config.abstract_file_based_spec import AbstractFileBasedSpec
16
+ from airbyte_cdk.sources.file_based.config.validate_config_transfer_modes import (
17
+ include_identities_stream,
18
+ preserve_directory_structure,
19
+ use_file_transfer,
20
+ )
16
21
  from airbyte_cdk.sources.file_based.remote_file import RemoteFile
17
22
 
18
23
 
@@ -128,41 +133,18 @@ class AbstractFileBasedStreamReader(ABC):
128
133
 
129
134
  def use_file_transfer(self) -> bool:
130
135
  if self.config:
131
- use_file_transfer = (
132
- hasattr(self.config.delivery_method, "delivery_type")
133
- and self.config.delivery_method.delivery_type == "use_file_transfer"
134
- )
135
- return use_file_transfer
136
- return False
137
-
138
- def use_records_transfer(self) -> bool:
139
- if self.config:
140
- use_records_transfer = (
141
- hasattr(self.config.delivery_method, "delivery_type")
142
- and self.config.delivery_method.delivery_type == "use_records_transfer"
143
- )
144
- return use_records_transfer
136
+ return use_file_transfer(self.config)
145
137
  return False
146
138
 
147
139
  def preserve_directory_structure(self) -> bool:
148
140
  # fall back to preserve subdirectories if config is not present or incomplete
149
- if (
150
- self.use_file_transfer()
151
- and self.config
152
- and hasattr(self.config.delivery_method, "preserve_directory_structure")
153
- and self.config.delivery_method.preserve_directory_structure is not None
154
- ):
155
- return self.config.delivery_method.preserve_directory_structure
141
+ if self.config:
142
+ return preserve_directory_structure(self.config)
156
143
  return True
157
144
 
158
- def sync_acl_permissions(self) -> bool:
159
- if (
160
- self.config
161
- and self.use_records_transfer()
162
- and hasattr(self.config.delivery_method, "sync_acl_permissions")
163
- and self.config.delivery_method.sync_acl_permissions is not None
164
- ):
165
- return self.config.delivery_method.sync_acl_permissions
145
+ def include_identities_stream(self) -> bool:
146
+ if self.config:
147
+ return include_identities_stream(self.config)
166
148
  return False
167
149
 
168
150
  @abstractmethod
@@ -203,16 +185,22 @@ class AbstractFileBasedStreamReader(ABC):
203
185
  absolute_file_path = path.abspath(local_file_path)
204
186
  return [file_relative_path, local_file_path, absolute_file_path]
205
187
 
188
+ @abstractmethod
206
189
  def get_file_acl_permissions(self, file: RemoteFile, logger: logging.Logger) -> Dict[str, Any]:
207
190
  """
208
191
  This is required for connectors that will support syncing
209
192
  ACL Permissions from files.
210
193
  """
211
- return {}
194
+ raise NotImplementedError(
195
+ f"{self.__class__.__name__} must implement get_file_acl_permissions()"
196
+ )
212
197
 
198
+ @abstractmethod
213
199
  def load_identity_groups(self, logger: logging.Logger) -> Iterable[Dict[str, Any]]:
214
200
  """
215
201
  This is required for connectors that will support syncing
216
202
  identities.
217
203
  """
218
- yield {}
204
+ raise NotImplementedError(
205
+ f"{self.__class__.__name__} must implement load_identity_groups()"
206
+ )
@@ -48,7 +48,7 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
48
48
 
49
49
  FILE_TRANSFER_KW = "use_file_transfer"
50
50
  PRESERVE_DIRECTORY_STRUCTURE_KW = "preserve_directory_structure"
51
- SYNC_ACL_PERMISSIONS_KW = "sync_acl_permissions"
51
+ PERMISSIONS_TRANSFER_KW = "use_permissions_transfer"
52
52
  FILES_KEY = "files"
53
53
  DATE_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
54
54
  ab_last_mod_col = "_ab_source_file_last_modified"
@@ -58,7 +58,7 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
58
58
  airbyte_columns = [ab_last_mod_col, ab_file_name_col]
59
59
  use_file_transfer = False
60
60
  preserve_directory_structure = True
61
- sync_acl_permissions = False
61
+ use_permissions_transfer = False
62
62
 
63
63
  def __init__(self, **kwargs: Any):
64
64
  if self.FILE_TRANSFER_KW in kwargs:
@@ -67,8 +67,8 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
67
67
  self.preserve_directory_structure = kwargs.pop(
68
68
  self.PRESERVE_DIRECTORY_STRUCTURE_KW, True
69
69
  )
70
- if self.SYNC_ACL_PERMISSIONS_KW in kwargs:
71
- self.sync_acl_permissions = kwargs.pop(self.SYNC_ACL_PERMISSIONS_KW, False)
70
+ if self.PERMISSIONS_TRANSFER_KW in kwargs:
71
+ self.use_permissions_transfer = kwargs.pop(self.PERMISSIONS_TRANSFER_KW, False)
72
72
  super().__init__(**kwargs)
73
73
 
74
74
  @property
@@ -110,7 +110,7 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
110
110
  self.ab_file_name_col: {"type": "string"},
111
111
  },
112
112
  }
113
- elif self.sync_acl_permissions:
113
+ elif self.use_permissions_transfer:
114
114
  return remote_file_permissions_schema
115
115
  else:
116
116
  return super()._filter_schema_invalid_properties(configured_catalog_json_schema)
@@ -194,7 +194,7 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
194
194
  yield stream_data_to_airbyte_message(
195
195
  self.name, record, is_file_transfer_message=True
196
196
  )
197
- elif self.sync_acl_permissions:
197
+ elif self.use_permissions_transfer:
198
198
  try:
199
199
  permissions_record = self.stream_reader.get_file_acl_permissions(
200
200
  file, logger=self.logger
@@ -314,7 +314,7 @@ class DefaultFileBasedStream(AbstractFileBasedStream, IncrementalMixin):
314
314
  def _get_raw_json_schema(self) -> JsonSchema:
315
315
  if self.use_file_transfer:
316
316
  return file_transfer_schema
317
- elif self.sync_acl_permissions:
317
+ elif self.use_permissions_transfer:
318
318
  return remote_file_permissions_schema
319
319
  elif self.config.input_schema:
320
320
  return self.config.get_input_schema() # type: ignore
@@ -4,7 +4,7 @@
4
4
 
5
5
  import traceback
6
6
  from functools import cache
7
- from typing import Any, Iterable, List, Mapping, MutableMapping, Optional
7
+ from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional
8
8
 
9
9
  from airbyte_protocol_dataclasses.models import SyncMode
10
10
 
@@ -68,7 +68,7 @@ class IdentitiesStream(Stream):
68
68
  stream_state: Optional[Mapping[str, Any]] = None,
69
69
  ) -> Iterable[Mapping[str, Any] | AirbyteMessage]:
70
70
  try:
71
- identity_groups = self.stream_reader.load_identity_groups(logger=self.logger)
71
+ identity_groups = self.load_identity_groups()
72
72
  for record in identity_groups:
73
73
  yield stream_data_to_airbyte_message(self.name, record)
74
74
  except AirbyteTracedException as exc:
@@ -84,6 +84,9 @@ class IdentitiesStream(Stream):
84
84
  ),
85
85
  )
86
86
 
87
+ def load_identity_groups(self) -> Iterable[Dict[str, Any]]:
88
+ return self.stream_reader.load_identity_groups(logger=self.logger)
89
+
87
90
  @cache
88
91
  def get_json_schema(self) -> JsonSchema:
89
92
  return remote_file_identity_schema
@@ -6,9 +6,6 @@ from abc import abstractmethod
6
6
  from datetime import datetime, timedelta, timezone
7
7
  from typing import Any, Callable, List, MutableMapping, Optional, Tuple
8
8
 
9
- import pendulum
10
- from pendulum.datetime import DateTime
11
-
12
9
  # FIXME We would eventually like the Concurrent package do be agnostic of the declarative package. However, this is a breaking change and
13
10
  # the goal in the short term is only to fix the issue we are seeing for source-declarative-manifest.
14
11
  from airbyte_cdk.sources.declarative.datetime.datetime_parser import DatetimeParser
@@ -17,6 +14,7 @@ from airbyte_cdk.sources.streams.concurrent.state_converters.abstract_stream_sta
17
14
  AbstractStreamStateConverter,
18
15
  ConcurrencyCompatibleStateType,
19
16
  )
17
+ from airbyte_cdk.utils.datetime_helpers import AirbyteDateTime, ab_datetime_now, ab_datetime_parse
20
18
 
21
19
 
22
20
  class DateTimeStreamStateConverter(AbstractStreamStateConverter):
@@ -36,7 +34,7 @@ class DateTimeStreamStateConverter(AbstractStreamStateConverter):
36
34
 
37
35
  @classmethod
38
36
  def get_end_provider(cls) -> Callable[[], datetime]:
39
- return lambda: datetime.now(timezone.utc)
37
+ return ab_datetime_now
40
38
 
41
39
  @abstractmethod
42
40
  def increment(self, timestamp: datetime) -> datetime: ...
@@ -136,10 +134,10 @@ class EpochValueConcurrentStreamStateConverter(DateTimeStreamStateConverter):
136
134
  return int(timestamp.timestamp())
137
135
 
138
136
  def parse_timestamp(self, timestamp: int) -> datetime:
139
- dt_object = pendulum.from_timestamp(timestamp)
140
- if not isinstance(dt_object, DateTime):
137
+ dt_object = AirbyteDateTime.fromtimestamp(timestamp, timezone.utc)
138
+ if not isinstance(dt_object, AirbyteDateTime):
141
139
  raise ValueError(
142
- f"DateTime object was expected but got {type(dt_object)} from pendulum.parse({timestamp})"
140
+ f"AirbyteDateTime object was expected but got {type(dt_object)} from AirbyteDateTime.fromtimestamp({timestamp})"
143
141
  )
144
142
  return dt_object
145
143
 
@@ -169,14 +167,25 @@ class IsoMillisConcurrentStreamStateConverter(DateTimeStreamStateConverter):
169
167
  def increment(self, timestamp: datetime) -> datetime:
170
168
  return timestamp + self._cursor_granularity
171
169
 
172
- def output_format(self, timestamp: datetime) -> Any:
173
- return timestamp.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
170
+ def output_format(self, timestamp: datetime) -> str:
171
+ """Format datetime with milliseconds always included.
172
+
173
+ Args:
174
+ timestamp: The datetime to format.
175
+
176
+ Returns:
177
+ str: ISO8601/RFC3339 formatted string with milliseconds.
178
+ """
179
+ dt = AirbyteDateTime.from_datetime(timestamp)
180
+ # Always include milliseconds, even if zero
181
+ millis = dt.microsecond // 1000 if dt.microsecond else 0
182
+ return f"{dt.year:04d}-{dt.month:02d}-{dt.day:02d}T{dt.hour:02d}:{dt.minute:02d}:{dt.second:02d}.{millis:03d}Z"
174
183
 
175
184
  def parse_timestamp(self, timestamp: str) -> datetime:
176
- dt_object = pendulum.parse(timestamp)
177
- if not isinstance(dt_object, DateTime):
185
+ dt_object = ab_datetime_parse(timestamp)
186
+ if not isinstance(dt_object, AirbyteDateTime):
178
187
  raise ValueError(
179
- f"DateTime object was expected but got {type(dt_object)} from pendulum.parse({timestamp})"
188
+ f"AirbyteDateTime object was expected but got {type(dt_object)} from parse({timestamp})"
180
189
  )
181
190
  return dt_object
182
191
 
@@ -184,7 +193,7 @@ class IsoMillisConcurrentStreamStateConverter(DateTimeStreamStateConverter):
184
193
  class CustomFormatConcurrentStreamStateConverter(IsoMillisConcurrentStreamStateConverter):
185
194
  """
186
195
  Datetime State converter that emits state according to the supplied datetime format. The converter supports reading
187
- incoming state in any valid datetime format via Pendulum.
196
+ incoming state in any valid datetime format using AirbyteDateTime parsing utilities.
188
197
  """
189
198
 
190
199
  def __init__(
@@ -223,17 +223,17 @@ class Stream(ABC):
223
223
  record_counter += 1
224
224
 
225
225
  checkpoint_interval = self.state_checkpoint_interval
226
- checkpoint = checkpoint_reader.get_checkpoint()
227
226
  if (
228
227
  should_checkpoint
229
228
  and checkpoint_interval
230
229
  and record_counter % checkpoint_interval == 0
231
- and checkpoint is not None
232
230
  ):
233
- airbyte_state_message = self._checkpoint_state(
234
- checkpoint, state_manager=state_manager
235
- )
236
- yield airbyte_state_message
231
+ checkpoint = checkpoint_reader.get_checkpoint()
232
+ if checkpoint:
233
+ airbyte_state_message = self._checkpoint_state(
234
+ checkpoint, state_manager=state_manager
235
+ )
236
+ yield airbyte_state_message
237
237
 
238
238
  if internal_config.is_limit_reached(record_counter):
239
239
  break
@@ -423,8 +423,6 @@ class HttpStream(Stream, CheckpointMixin, ABC):
423
423
  stream_slice: Optional[Mapping[str, Any]] = None,
424
424
  stream_state: Optional[Mapping[str, Any]] = None,
425
425
  ) -> Iterable[StreamData]:
426
- partition, _, _ = self._extract_slice_fields(stream_slice=stream_slice)
427
-
428
426
  stream_state = stream_state or {}
429
427
  pagination_complete = False
430
428
  next_page_token = None
@@ -438,6 +436,7 @@ class HttpStream(Stream, CheckpointMixin, ABC):
438
436
 
439
437
  cursor = self.get_cursor()
440
438
  if cursor and isinstance(cursor, SubstreamResumableFullRefreshCursor):
439
+ partition, _, _ = self._extract_slice_fields(stream_slice=stream_slice)
441
440
  # Substreams checkpoint state by marking an entire parent partition as completed so that on the subsequent attempt
442
441
  # after a failure, completed parents are skipped and the sync can make progress
443
442
  cursor.close_slice(StreamSlice(cursor_slice={}, partition=partition))