|
13 | 13 | from dagster import ( |
14 | 14 | AssetExecutionContext, |
15 | 15 | AssetMaterialization, |
| 16 | + Config, |
16 | 17 | Definitions, |
17 | 18 | Failure, |
18 | 19 | InitResourceContext, |
@@ -928,6 +929,29 @@ def _sync_and_poll( |
928 | 929 | return FivetranOutput(connector_details=final_details, schema_config=schema_config_details) |
929 | 930 |
|
930 | 931 |
|
| 932 | +class FivetranSyncConfig(Config): |
| 933 | + """Configuration for controlling Fivetran sync behavior. |
| 934 | +
|
| 935 | + Attributes: |
| 936 | + resync: If True, performs a historical resync. If False, performs a normal sync. |
| 937 | + resync_parameters: Optional parameters to control which tables to resync. |
| 938 | + If not provided with resync=True, all tables will be resynced. |
| 939 | + Example: {"schema_name": ["table1", "table2"], "another_schema": ["table3"]} |
| 940 | + """ |
| 941 | + |
| 942 | + resync: bool = Field( |
| 943 | + default=False, |
| 944 | + description="Whether to perform a historical resync instead of a normal sync", |
| 945 | + ) |
| 946 | + resync_parameters: Optional[dict[str, Any]] = Field( |
| 947 | + default=None, |
| 948 | + description=( |
| 949 | + "Optional parameters to control which tables to resync. " |
| 950 | + "If not provided with resync=True, all tables will be resynced." |
| 951 | + ), |
| 952 | + ) |
| 953 | + |
| 954 | + |
931 | 955 | class FivetranWorkspace(ConfigurableResource): |
932 | 956 | """This class represents a Fivetran workspace and provides utilities |
933 | 957 | to interact with Fivetran APIs. |
@@ -1185,22 +1209,84 @@ def _generate_materialization( |
1185 | 1209 |
|
1186 | 1210 | @public |
1187 | 1211 | def sync_and_poll( |
1188 | | - self, context: AssetExecutionContext |
| 1212 | + self, |
| 1213 | + context: AssetExecutionContext, |
| 1214 | + config: Optional[FivetranSyncConfig] = None, |
1189 | 1215 | ) -> FivetranEventIterator[Union[AssetMaterialization, MaterializeResult]]: |
1190 | 1216 | """Executes a sync and poll process to materialize Fivetran assets. |
1191 | 1217 | This method can only be used in the context of an asset execution. |
1192 | 1218 |
|
1193 | 1219 | Args: |
1194 | 1220 | context (AssetExecutionContext): The execution context |
1195 | 1221 | from within `@fivetran_assets`. |
| 1222 | + config (Optional[FivetranSyncConfig]): Optional configuration to control sync behavior. |
| 1223 | + If config.resync is True, performs a historical resync instead of a normal sync. |
| 1224 | + If config.resync_parameters is provided, only the specified tables will be resynced. |
1196 | 1225 |
|
1197 | 1226 | Returns: |
1198 | 1227 | Iterator[Union[AssetMaterialization, MaterializeResult]]: An iterator of MaterializeResult |
1199 | 1228 | or AssetMaterialization. |
| 1229 | +
|
| 1230 | + Examples: |
| 1231 | + Normal sync (without config): |
| 1232 | +
|
| 1233 | + .. code-block:: python |
| 1234 | +
|
| 1235 | + from dagster import AssetExecutionContext |
| 1236 | + from dagster_fivetran import FivetranWorkspace, fivetran_assets |
| 1237 | +
|
| 1238 | + @fivetran_assets(connector_id="my_connector", workspace=fivetran_workspace) |
| 1239 | + def my_fivetran_assets(context: AssetExecutionContext, fivetran: FivetranWorkspace): |
| 1240 | + yield from fivetran.sync_and_poll(context=context) |
| 1241 | +
|
| 1242 | + Historical resync of specific tables (config passed at runtime): |
| 1243 | +
|
| 1244 | + .. code-block:: python |
| 1245 | +
|
| 1246 | + from dagster import AssetExecutionContext |
| 1247 | + from dagster_fivetran import FivetranWorkspace, FivetranSyncConfig, fivetran_assets |
| 1248 | +
|
| 1249 | + @fivetran_assets(connector_id="my_connector", workspace=fivetran_workspace) |
| 1250 | + def my_fivetran_assets( |
| 1251 | + context: AssetExecutionContext, |
| 1252 | + fivetran: FivetranWorkspace, |
| 1253 | + config: FivetranSyncConfig, |
| 1254 | + ): |
| 1255 | + # When materializing, pass config with: |
| 1256 | + # resync=True |
| 1257 | + # resync_parameters={"schema_name": ["table1", "table2"]} |
| 1258 | + yield from fivetran.sync_and_poll(context=context, config=config) |
| 1259 | +
|
| 1260 | + Full historical resync (config passed at runtime): |
| 1261 | +
|
| 1262 | + .. code-block:: python |
| 1263 | +
|
| 1264 | + from dagster import AssetExecutionContext |
| 1265 | + from dagster_fivetran import FivetranWorkspace, FivetranSyncConfig, fivetran_assets |
| 1266 | +
|
| 1267 | + @fivetran_assets(connector_id="my_connector", workspace=fivetran_workspace) |
| 1268 | + def my_fivetran_assets( |
| 1269 | + context: AssetExecutionContext, |
| 1270 | + fivetran: FivetranWorkspace, |
| 1271 | + config: FivetranSyncConfig, |
| 1272 | + ): |
| 1273 | + # When materializing, pass config with resync=True to resync all tables |
| 1274 | + yield from fivetran.sync_and_poll(context=context, config=config) |
1200 | 1275 | """ |
1201 | | - return FivetranEventIterator( |
1202 | | - events=self._sync_and_poll(context=context), fivetran_workspace=self, context=context |
1203 | | - ) |
| 1276 | + if config and config.resync: |
| 1277 | + return FivetranEventIterator( |
| 1278 | + events=self._resync_and_poll( |
| 1279 | + context=context, resync_parameters=config.resync_parameters |
| 1280 | + ), |
| 1281 | + fivetran_workspace=self, |
| 1282 | + context=context, |
| 1283 | + ) |
| 1284 | + else: |
| 1285 | + return FivetranEventIterator( |
| 1286 | + events=self._sync_and_poll(context=context), |
| 1287 | + fivetran_workspace=self, |
| 1288 | + context=context, |
| 1289 | + ) |
1204 | 1290 |
|
1205 | 1291 | def _sync_and_poll(self, context: AssetExecutionContext): |
1206 | 1292 | assets_def = context.assets_def |
@@ -1245,6 +1331,54 @@ def _sync_and_poll(self, context: AssetExecutionContext): |
1245 | 1331 | if unmaterialized_asset_keys: |
1246 | 1332 | context.log.warning(f"Assets were not materialized: {unmaterialized_asset_keys}") |
1247 | 1333 |
|
| 1334 | + def _resync_and_poll( |
| 1335 | + self, |
| 1336 | + context: AssetExecutionContext, |
| 1337 | + resync_parameters: Optional[Mapping[str, Sequence[str]]] = None, |
| 1338 | + ): |
| 1339 | + assets_def = context.assets_def |
| 1340 | + dagster_fivetran_translator = get_translator_from_fivetran_assets(assets_def) |
| 1341 | + connector_id = next( |
| 1342 | + check.not_none(FivetranMetadataSet.extract(spec.metadata).connector_id) |
| 1343 | + for spec in assets_def.specs |
| 1344 | + ) |
| 1345 | + |
| 1346 | + client = self.get_client() |
| 1347 | + fivetran_output = client.resync_and_poll( |
| 1348 | + connector_id=connector_id, |
| 1349 | + resync_parameters=resync_parameters, |
| 1350 | + ) |
| 1351 | + |
| 1352 | + # The FivetranOutput is None if the connector hasn't been synced |
| 1353 | + if not fivetran_output: |
| 1354 | + context.log.warning( |
| 1355 | + f"The connector with ID {connector_id} is currently paused and so it has not been resynced. " |
| 1356 | + f"Make sure that your connector is enabled before resyncing it with Dagster." |
| 1357 | + ) |
| 1358 | + return |
| 1359 | + |
| 1360 | + materialized_asset_keys = set() |
| 1361 | + for materialization in self._generate_materialization( |
| 1362 | + fivetran_output=fivetran_output, dagster_fivetran_translator=dagster_fivetran_translator |
| 1363 | + ): |
| 1364 | + # Scan through all tables actually created, if it was expected then emit a MaterializeResult. |
| 1365 | + # Otherwise, emit a runtime AssetMaterialization. |
| 1366 | + if materialization.asset_key in context.selected_asset_keys: |
| 1367 | + yield MaterializeResult( |
| 1368 | + asset_key=materialization.asset_key, metadata=materialization.metadata |
| 1369 | + ) |
| 1370 | + materialized_asset_keys.add(materialization.asset_key) |
| 1371 | + else: |
| 1372 | + context.log.warning( |
| 1373 | + f"An unexpected asset was materialized: {materialization.asset_key}. " |
| 1374 | + f"Yielding a materialization event." |
| 1375 | + ) |
| 1376 | + yield materialization |
| 1377 | + |
| 1378 | + unmaterialized_asset_keys = context.selected_asset_keys - materialized_asset_keys |
| 1379 | + if unmaterialized_asset_keys: |
| 1380 | + context.log.warning(f"Assets were not materialized: {unmaterialized_asset_keys}") |
| 1381 | + |
1248 | 1382 |
|
1249 | 1383 | def load_fivetran_asset_specs( |
1250 | 1384 | workspace: FivetranWorkspace, |
|
0 commit comments