Skip to content

Commit 7e4d73f

Browse files
committed
file-based: move permissions transfer mode to general with abstract Identitie stream, so file based Identity stream becomes an implementation
1 parent a7081d3 commit 7e4d73f

File tree

6 files changed

+117
-79
lines changed

6 files changed

+117
-79
lines changed

airbyte_cdk/sources/file_based/config/abstract_file_based_spec.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,10 @@
1111

1212
from airbyte_cdk import OneOfOptionConfig
1313
from airbyte_cdk.sources.file_based.config.file_based_stream_config import FileBasedStreamConfig
14+
from airbyte_cdk.sources.specs.transfer_modes import DeliverPermissions
1415
from airbyte_cdk.sources.utils import schema_helpers
1516

1617

17-
class DeliverPermissions(BaseModel):
18-
class Config(OneOfOptionConfig):
19-
title = "Replicate Permissions ACL"
20-
description = "Sends one identity stream and one for more permissions (ACL) streams to the destination. This data can be used in downstream systems to recreate permission restrictions mirroring the original source."
21-
discriminator = "delivery_type"
22-
23-
delivery_type: Literal["use_permissions_transfer"] = Field(
24-
"use_permissions_transfer", const=True
25-
)
26-
27-
include_identities_stream: bool = Field(
28-
title="Include Identity Stream",
29-
description="This data can be used in downstream systems to recreate permission restrictions mirroring the original source",
30-
default=True,
31-
)
32-
33-
3418
class DeliverRecords(BaseModel):
3519
class Config(OneOfOptionConfig):
3620
title = "Replicate Records"

airbyte_cdk/sources/file_based/file_based_source.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
from airbyte_cdk.sources.file_based.stream import (
5959
AbstractFileBasedStream,
6060
DefaultFileBasedStream,
61-
IdentitiesStream,
61+
FileIdentities,
6262
)
6363
from airbyte_cdk.sources.file_based.stream.concurrent.adapters import FileBasedStreamFacade
6464
from airbyte_cdk.sources.file_based.stream.concurrent.cursor import (
@@ -67,7 +67,6 @@
6767
FileBasedFinalStateCursor,
6868
)
6969
from airbyte_cdk.sources.file_based.stream.cursor import AbstractFileBasedCursor
70-
from airbyte_cdk.sources.file_based.stream.identities_stream import IDENTITIES_STREAM_NAME
7170
from airbyte_cdk.sources.message.repository import InMemoryMessageRepository, MessageRepository
7271
from airbyte_cdk.sources.streams import Stream
7372
from airbyte_cdk.sources.streams.concurrent.cursor import CursorField
@@ -169,7 +168,7 @@ def check_connection(
169168
errors = []
170169
tracebacks = []
171170
for stream in streams:
172-
if isinstance(stream, IdentitiesStream):
171+
if isinstance(stream, FileIdentities):
173172
identity = next(iter(stream.load_identity_groups()))
174173
if not identity:
175174
errors.append(
@@ -341,8 +340,8 @@ def _make_default_stream(
341340
def _make_identities_stream(
342341
self,
343342
) -> Stream:
344-
return IdentitiesStream(
345-
catalog_schema=self.stream_schemas.get(IDENTITIES_STREAM_NAME),
343+
return FileIdentities(
344+
catalog_schema=self.stream_schemas.get(FileIdentities.IDENTITIES_STREAM_NAME),
346345
stream_reader=self.stream_reader,
347346
discovery_policy=self.discovery_policy,
348347
errors_collector=self.errors_collector,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from airbyte_cdk.sources.file_based.stream.abstract_file_based_stream import AbstractFileBasedStream
22
from airbyte_cdk.sources.file_based.stream.default_file_based_stream import DefaultFileBasedStream
3-
from airbyte_cdk.sources.file_based.stream.identities_stream import IdentitiesStream
3+
from airbyte_cdk.sources.file_based.stream.identities_stream import FileIdentities
44

5-
__all__ = ["AbstractFileBasedStream", "DefaultFileBasedStream", "IdentitiesStream"]
5+
__all__ = ["AbstractFileBasedStream", "DefaultFileBasedStream", "FileIdentities"]

airbyte_cdk/sources/file_based/stream/identities_stream.py

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,18 @@
22
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
33
#
44

5-
import traceback
65
from functools import cache
7-
from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional
6+
from typing import Any, Dict, Iterable, Mapping, MutableMapping, Optional
87

9-
from airbyte_protocol_dataclasses.models import SyncMode
10-
11-
from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, Level
12-
from airbyte_cdk.models import Type as MessageType
8+
from airbyte_cdk.sources.streams.permissions.identities import Identities
139
from airbyte_cdk.sources.file_based.config.file_based_stream_config import PrimaryKeyType
1410
from airbyte_cdk.sources.file_based.discovery_policy import AbstractDiscoveryPolicy
15-
from airbyte_cdk.sources.file_based.exceptions import FileBasedErrorsCollector, FileBasedSourceError
11+
from airbyte_cdk.sources.file_based.exceptions import FileBasedErrorsCollector
1612
from airbyte_cdk.sources.file_based.file_based_stream_reader import AbstractFileBasedStreamReader
17-
from airbyte_cdk.sources.file_based.types import StreamSlice
18-
from airbyte_cdk.sources.streams import Stream
19-
from airbyte_cdk.sources.streams.checkpoint import Cursor
2013
from airbyte_cdk.sources.streams.core import JsonSchema
21-
from airbyte_cdk.sources.utils.record_helper import stream_data_to_airbyte_message
22-
from airbyte_cdk.utils.traced_exception import AirbyteTracedException
23-
24-
IDENTITIES_STREAM_NAME = "identities"
2514

2615

27-
class IdentitiesStream(Stream):
16+
class FileIdentities(Identities):
2817
"""
2918
The identities stream. A full refresh stream to sync identities from a certain domain.
3019
The stream reader manage the logic to get such data, which is implemented on connector side.
@@ -46,53 +35,13 @@ def __init__(
4635
self.errors_collector = errors_collector
4736
self._cursor: MutableMapping[str, Any] = {}
4837

49-
@property
50-
def state(self) -> MutableMapping[str, Any]:
51-
return self._cursor
52-
53-
@state.setter
54-
def state(self, value: MutableMapping[str, Any]) -> None:
55-
"""State setter, accept state serialized by state getter."""
56-
self._cursor = value
57-
5838
@property
5939
def primary_key(self) -> PrimaryKeyType:
6040
return None
6141

62-
def read_records(
63-
self,
64-
sync_mode: SyncMode,
65-
cursor_field: Optional[List[str]] = None,
66-
stream_slice: Optional[StreamSlice] = None,
67-
stream_state: Optional[Mapping[str, Any]] = None,
68-
) -> Iterable[Mapping[str, Any] | AirbyteMessage]:
69-
try:
70-
identity_groups = self.load_identity_groups()
71-
for record in identity_groups:
72-
yield stream_data_to_airbyte_message(self.name, record)
73-
except AirbyteTracedException as exc:
74-
# Re-raise the exception to stop the whole sync immediately as this is a fatal error
75-
raise exc
76-
except Exception:
77-
yield AirbyteMessage(
78-
type=MessageType.LOG,
79-
log=AirbyteLogMessage(
80-
level=Level.ERROR,
81-
message=f"{FileBasedSourceError.ERROR_PARSING_RECORD.value} stream={self.name}",
82-
stack_trace=traceback.format_exc(),
83-
),
84-
)
85-
8642
def load_identity_groups(self) -> Iterable[Dict[str, Any]]:
8743
return self.stream_reader.load_identity_groups(logger=self.logger)
8844

8945
@cache
9046
def get_json_schema(self) -> JsonSchema:
9147
return self.stream_reader.REMOTE_FILE_IDENTITY_SCHEMA
92-
93-
@property
94-
def name(self) -> str:
95-
return IDENTITIES_STREAM_NAME
96-
97-
def get_cursor(self) -> Optional[Cursor]:
98-
return None
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#
2+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
3+
#
4+
5+
from typing import Literal
6+
7+
from pydantic.v1 import AnyUrl, BaseModel, Field
8+
from airbyte_cdk import OneOfOptionConfig
9+
10+
11+
class DeliverPermissions(BaseModel):
12+
class Config(OneOfOptionConfig):
13+
title = "Replicate Permissions ACL"
14+
description = "Sends one identity stream and one for more permissions (ACL) streams to the destination. This data can be used in downstream systems to recreate permission restrictions mirroring the original source."
15+
discriminator = "delivery_type"
16+
17+
delivery_type: Literal["use_permissions_transfer"] = Field(
18+
"use_permissions_transfer", const=True
19+
)
20+
21+
include_identities_stream: bool = Field(
22+
title="Include Identity Stream",
23+
description="This data can be used in downstream systems to recreate permission restrictions mirroring the original source",
24+
default=True,
25+
)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#
2+
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
3+
#
4+
5+
import traceback
6+
from abc import ABC, abstractmethod
7+
from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional
8+
9+
from airbyte_protocol_dataclasses.models import SyncMode
10+
11+
from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, Level
12+
from airbyte_cdk.models import Type as MessageType
13+
from airbyte_cdk.sources.streams import Stream
14+
from airbyte_cdk.sources.streams.checkpoint import Cursor
15+
from airbyte_cdk.sources.utils.record_helper import stream_data_to_airbyte_message
16+
from airbyte_cdk.utils.traced_exception import AirbyteTracedException
17+
18+
DEFAULT_IDENTITIES_STREAM_NAME = "identities"
19+
20+
21+
class Identities(Stream, ABC):
22+
"""
23+
The identities stream. A full refresh stream to sync identities from a certain domain.
24+
The load_identity_groups method manage the logic to get such data.
25+
"""
26+
27+
IDENTITIES_STREAM_NAME = DEFAULT_IDENTITIES_STREAM_NAME
28+
29+
is_resumable = False
30+
31+
def __init__(
32+
self,
33+
catalog_schema: Optional[Mapping[str, Any]],
34+
):
35+
super().__init__()
36+
self.catalog_schema = catalog_schema
37+
self._cursor: MutableMapping[str, Any] = {}
38+
39+
@property
40+
def state(self) -> MutableMapping[str, Any]:
41+
return self._cursor
42+
43+
@state.setter
44+
def state(self, value: MutableMapping[str, Any]) -> None:
45+
"""State setter, accept state serialized by state getter."""
46+
self._cursor = value
47+
48+
def read_records(
49+
self,
50+
sync_mode: SyncMode,
51+
cursor_field: Optional[List[str]] = None,
52+
stream_slice: Optional[Mapping[str, Any]] = None,
53+
stream_state: Optional[Mapping[str, Any]] = None,
54+
) -> Iterable[Mapping[str, Any] | AirbyteMessage]:
55+
try:
56+
identity_groups = self.load_identity_groups()
57+
for record in identity_groups:
58+
yield stream_data_to_airbyte_message(self.name, record)
59+
except AirbyteTracedException as exc:
60+
# Re-raise the exception to stop the whole sync immediately as this is a fatal error
61+
raise exc
62+
except Exception as e:
63+
yield AirbyteMessage(
64+
type=MessageType.LOG,
65+
log=AirbyteLogMessage(
66+
level=Level.ERROR,
67+
message=f"Error trying to read identities: {e} stream={self.name}",
68+
stack_trace=traceback.format_exc(),
69+
),
70+
)
71+
72+
@abstractmethod
73+
def load_identity_groups(self) -> Iterable[Dict[str, Any]]:
74+
raise NotImplementedError("Implement this method to read identity records")
75+
76+
@property
77+
def name(self) -> str:
78+
return self.IDENTITIES_STREAM_NAME
79+
80+
def get_cursor(self) -> Optional[Cursor]:
81+
return None

0 commit comments

Comments
 (0)