dq-export-connector 0.1.0.dev1__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.
Files changed (28) hide show
  1. dq_export_connector-0.1.0.dev1/.gitignore +63 -0
  2. dq_export_connector-0.1.0.dev1/PKG-INFO +47 -0
  3. dq_export_connector-0.1.0.dev1/README.md +37 -0
  4. dq_export_connector-0.1.0.dev1/dq_export_connector/__init__.py +0 -0
  5. dq_export_connector-0.1.0.dev1/dq_export_connector/_models/__init__.py +0 -0
  6. dq_export_connector-0.1.0.dev1/dq_export_connector/_models/bucket_validation_result.py +63 -0
  7. dq_export_connector-0.1.0.dev1/dq_export_connector/_models/export_connector_credentials.py +84 -0
  8. dq_export_connector-0.1.0.dev1/dq_export_connector/_models/export_receipt.py +152 -0
  9. dq_export_connector-0.1.0.dev1/dq_export_connector/_models/export_worker_config.py +183 -0
  10. dq_export_connector-0.1.0.dev1/dq_export_connector/bucket/__init__.py +0 -0
  11. dq_export_connector-0.1.0.dev1/dq_export_connector/bucket/datasets.py +71 -0
  12. dq_export_connector-0.1.0.dev1/dq_export_connector/bucket/export.py +215 -0
  13. dq_export_connector-0.1.0.dev1/dq_export_connector/bucket/paths.py +83 -0
  14. dq_export_connector-0.1.0.dev1/dq_export_connector/bucket/store.py +179 -0
  15. dq_export_connector-0.1.0.dev1/dq_export_connector/bucket/upload.py +46 -0
  16. dq_export_connector-0.1.0.dev1/dq_export_connector/bucket/validate.py +71 -0
  17. dq_export_connector-0.1.0.dev1/dq_export_connector/log.py +45 -0
  18. dq_export_connector-0.1.0.dev1/dq_export_connector/redact.py +51 -0
  19. dq_export_connector-0.1.0.dev1/dq_export_connector/runner.py +87 -0
  20. dq_export_connector-0.1.0.dev1/pyproject.toml +38 -0
  21. dq_export_connector-0.1.0.dev1/scripts/codegen-models.sh +22 -0
  22. dq_export_connector-0.1.0.dev1/tests/test_live_buckets.py +135 -0
  23. dq_export_connector-0.1.0.dev1/tests/test_models_are_current.py +15 -0
  24. dq_export_connector-0.1.0.dev1/tests/test_paths.py +60 -0
  25. dq_export_connector-0.1.0.dev1/tests/test_redact.py +28 -0
  26. dq_export_connector-0.1.0.dev1/tests/test_runner.py +194 -0
  27. dq_export_connector-0.1.0.dev1/tests/test_upload.py +22 -0
  28. dq_export_connector-0.1.0.dev1/uv.lock +1189 -0
@@ -0,0 +1,63 @@
1
+ .DS_Store
2
+ **/build/
3
+ .idea/
4
+ .vscode/*
5
+ !.vscode/tasks.json
6
+ target/
7
+ node_modules/
8
+ result
9
+ /result-*
10
+ **/*.o
11
+ env
12
+ venv
13
+ .venv
14
+ .direnv
15
+ **/perf.data*
16
+ infra/packages/hadoop/binary.tar.gz
17
+ .terraform
18
+ config.tf.json
19
+ *.tfstate.*
20
+ *.tfstate
21
+ **/.deploy-gc
22
+ .vagrant
23
+ .pnpm-debug.log
24
+ .yarn
25
+ __pycache__
26
+ trusted/dcr-compiler/ddc/src/data_lab/**/*.png
27
+ trusted/dcr-compiler/ddc/src/data_lab/**/*.json
28
+ e2e-tests/frontend/confidence-check-config.json
29
+ .terraform.lock.hcl
30
+ dmp/analysis-scripts/results/*
31
+ .env
32
+ !tools/dqdev/internal/target/
33
+ docusaurus/style-guide.md
34
+ **/.claude/settings.local.json
35
+ **/.claude/agent-memory-local/
36
+ .claude/*.lock
37
+ .claude/worktrees/
38
+ **/CLAUDE.local.md
39
+ **/*.parquet/**
40
+ .pnpm-store/
41
+ .turbo/
42
+
43
+ # Agentic-coding workflow (local, not part of this repo)
44
+ .workflow/
45
+ .workflow-backup-*
46
+
47
+ # Local-only Google Ads/DV360 test scripts (kept out of the PR)
48
+ dmp/dqdmp/scripts/
49
+
50
+ # Playwright MCP screenshots (local only)
51
+ .playwright-mcp/
52
+
53
+ # Claude Design sync build artifacts (local only).
54
+ **/.design-sync/sb-reference/
55
+ **/.design-sync/learnings/
56
+ **/.design-sync/.cache/
57
+ **/.design-sync/node_modules
58
+ .ds-sync/
59
+ ds-bundle/
60
+
61
+ # DMP creation-prefill tests (local only)
62
+ avato-backend/frontend/decentriq-platform/src/features/dmp/models/creationPrefill.test.ts
63
+ avato-backend/frontend/decentriq-platform/src/features/dmp/utils/creationPrefill/*.test.ts
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.5
2
+ Name: dq-export-connector
3
+ Version: 0.1.0.dev1
4
+ Summary: Worker logic for the Export Connector DCR.
5
+ Author: Decentriq
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: obstore<0.12,>=0.11.1
8
+ Requires-Dist: pydantic<3,>=2.7
9
+ Description-Content-Type: text/markdown
10
+
11
+ # dq-export-connector
12
+
13
+ Worker logic for the **Export Connector DCR**: one package for every
14
+ Connector Kind, each in its own sub-package. The **bucket** kind uploads each
15
+ Input's dataset to S3, Azure Blob or GCS through
16
+ [`obstore`](https://developmentseed.org/obstore/).
17
+
18
+ ddc pins this package by hash in a fully locked `requirements.txt` and runs
19
+ `dq_export_connector.runner.main` in the task's Python environment. It is
20
+ published to PyPI, since the compiled graph installs it from there.
21
+
22
+ - `dq_export_connector.runner`: the entry point. Reads `/input/config.json`
23
+ and `/input/credentials.json`, sets up logging, and dispatches on the
24
+ Connector Kind and the action.
25
+ - `dq_export_connector.redact`: keeps every value from the sealed half, and
26
+ URL query strings, out of log lines and result messages.
27
+ - `dq_export_connector.bucket`: the bucket kind (`ValidateConnection` and
28
+ `Export`).
29
+ - `dq_export_connector._models`: pydantic models generated from ddc's JSON
30
+ schemas, so the config the worker reads and the receipt and validation
31
+ result it writes match ddc's Rust types. Regenerate them with
32
+ `scripts/codegen-models.sh` after the types change and
33
+ `decentriq_dcr_compiler` has been rebuilt; a test fails while they are
34
+ stale.
35
+
36
+ ```sh
37
+ uv run pytest # unit tests, against obstore's in-memory store
38
+ uv run pytest -m live # against real buckets, credentials from the environment
39
+ uv run mypy dq_export_connector tests
40
+ ```
41
+
42
+ ## Releasing
43
+
44
+ Bump `version` in `pyproject.toml`, then push a `dq-export-connector-v<version>`
45
+ tag; the `Publish dq-export-connector to PyPI` workflow tests, builds and uploads
46
+ it. Use `.devN` versions while developing; PyPI releases can't be replaced.
47
+ ddc then pins the new version in a new `export_connector/v0/worker/<version>/`.
@@ -0,0 +1,37 @@
1
+ # dq-export-connector
2
+
3
+ Worker logic for the **Export Connector DCR**: one package for every
4
+ Connector Kind, each in its own sub-package. The **bucket** kind uploads each
5
+ Input's dataset to S3, Azure Blob or GCS through
6
+ [`obstore`](https://developmentseed.org/obstore/).
7
+
8
+ ddc pins this package by hash in a fully locked `requirements.txt` and runs
9
+ `dq_export_connector.runner.main` in the task's Python environment. It is
10
+ published to PyPI, since the compiled graph installs it from there.
11
+
12
+ - `dq_export_connector.runner`: the entry point. Reads `/input/config.json`
13
+ and `/input/credentials.json`, sets up logging, and dispatches on the
14
+ Connector Kind and the action.
15
+ - `dq_export_connector.redact`: keeps every value from the sealed half, and
16
+ URL query strings, out of log lines and result messages.
17
+ - `dq_export_connector.bucket`: the bucket kind (`ValidateConnection` and
18
+ `Export`).
19
+ - `dq_export_connector._models`: pydantic models generated from ddc's JSON
20
+ schemas, so the config the worker reads and the receipt and validation
21
+ result it writes match ddc's Rust types. Regenerate them with
22
+ `scripts/codegen-models.sh` after the types change and
23
+ `decentriq_dcr_compiler` has been rebuilt; a test fails while they are
24
+ stale.
25
+
26
+ ```sh
27
+ uv run pytest # unit tests, against obstore's in-memory store
28
+ uv run pytest -m live # against real buckets, credentials from the environment
29
+ uv run mypy dq_export_connector tests
30
+ ```
31
+
32
+ ## Releasing
33
+
34
+ Bump `version` in `pyproject.toml`, then push a `dq-export-connector-v<version>`
35
+ tag; the `Publish dq-export-connector to PyPI` workflow tests, builds and uploads
36
+ it. Use `.devN` versions while developing; PyPI releases can't be replaced.
37
+ ddc then pins the new version in a new `export_connector/v0/worker/<version>/`.
@@ -0,0 +1,63 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: bucket_validation_result.json
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Annotated, Literal
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field, RootModel
9
+
10
+
11
+ class BucketValidationResult1(BaseModel):
12
+ model_config = ConfigDict(
13
+ populate_by_name=True,
14
+ )
15
+ status: Literal['OK']
16
+
17
+
18
+ class ExportMessage(BaseModel):
19
+ model_config = ConfigDict(
20
+ populate_by_name=True,
21
+ )
22
+ code: Annotated[
23
+ str,
24
+ Field(
25
+ description='SCREAMING_SNAKE_CASE, such as `ACCESS_DENIED`. A string rather than an\nenum, so a worker release can add codes that readers built against an\nolder ddc still accept.'
26
+ ),
27
+ ]
28
+ message: Annotated[
29
+ str,
30
+ Field(
31
+ description='Safe to show to the user: no credentials and no dataset values.'
32
+ ),
33
+ ]
34
+
35
+
36
+ class BucketValidationResult2(BaseModel):
37
+ model_config = ConfigDict(
38
+ populate_by_name=True,
39
+ )
40
+ status: Literal['OK_WITH_WARNINGS']
41
+ warnings: list[ExportMessage]
42
+
43
+
44
+ class BucketValidationResult3(BaseModel):
45
+ model_config = ConfigDict(
46
+ populate_by_name=True,
47
+ )
48
+ errors: list[ExportMessage]
49
+ status: Literal['ERROR']
50
+
51
+
52
+ class BucketValidationResult(
53
+ RootModel[
54
+ BucketValidationResult1 | BucketValidationResult2 | BucketValidationResult3
55
+ ]
56
+ ):
57
+ root: Annotated[
58
+ BucketValidationResult1 | BucketValidationResult2 | BucketValidationResult3,
59
+ Field(
60
+ description='The result of a Bucket `ValidateConnection`. Connection problems are\nreported here, and the task still succeeds.',
61
+ title='BucketValidationResult',
62
+ ),
63
+ ]
@@ -0,0 +1,84 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: export_connector_credentials.json
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Annotated
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field, RootModel
9
+
10
+
11
+ class S3(BaseModel):
12
+ model_config = ConfigDict(
13
+ populate_by_name=True,
14
+ )
15
+ access_key: Annotated[str, Field(alias='accessKey')]
16
+ secret_key: Annotated[str, Field(alias='secretKey')]
17
+
18
+
19
+ class BucketCredentials1(BaseModel):
20
+ model_config = ConfigDict(
21
+ extra='forbid',
22
+ populate_by_name=True,
23
+ )
24
+ s3: S3
25
+
26
+
27
+ class AzureBlob(BaseModel):
28
+ model_config = ConfigDict(
29
+ populate_by_name=True,
30
+ )
31
+ sas_token: Annotated[
32
+ str,
33
+ Field(
34
+ alias='sasToken',
35
+ description='A container SAS token — the scoped, expiring grant Azure recommends\nover an account key. Only the grant: the account and container it\napplies to are pinned by the signed `BucketConnection`, so rotating\nthe token can never repoint the connector.',
36
+ ),
37
+ ]
38
+
39
+
40
+ class BucketCredentials2(BaseModel):
41
+ model_config = ConfigDict(
42
+ extra='forbid',
43
+ populate_by_name=True,
44
+ )
45
+ azure_blob: Annotated[AzureBlob, Field(alias='azureBlob')]
46
+
47
+
48
+ class Gcs(BaseModel):
49
+ model_config = ConfigDict(
50
+ populate_by_name=True,
51
+ )
52
+ service_account_json: Annotated[str, Field(alias='serviceAccountJson')]
53
+
54
+
55
+ class BucketCredentials3(BaseModel):
56
+ model_config = ConfigDict(
57
+ extra='forbid',
58
+ populate_by_name=True,
59
+ )
60
+ gcs: Gcs
61
+
62
+
63
+ class BucketCredentials(
64
+ RootModel[BucketCredentials1 | BucketCredentials2 | BucketCredentials3]
65
+ ):
66
+ root: BucketCredentials1 | BucketCredentials2 | BucketCredentials3
67
+
68
+
69
+ class ExportConnectorCredentials1(BaseModel):
70
+ model_config = ConfigDict(
71
+ extra='forbid',
72
+ populate_by_name=True,
73
+ )
74
+ bucket: BucketCredentials
75
+
76
+
77
+ class ExportConnectorCredentials(RootModel[ExportConnectorCredentials1]):
78
+ root: Annotated[
79
+ ExportConnectorCredentials1,
80
+ Field(
81
+ description='Keyed by Connector Kind, in step with [`ExportConnectorKind`]. The worker\nreads it at `/input/credentials.json`.',
82
+ title='ExportConnectorCredentials',
83
+ ),
84
+ ]
@@ -0,0 +1,152 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: export_receipt.json
3
+
4
+ from __future__ import annotations
5
+
6
+ from enum import Enum
7
+ from typing import Annotated, Literal
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field, RootModel
10
+
11
+
12
+ class ExportMessage(BaseModel):
13
+ model_config = ConfigDict(
14
+ populate_by_name=True,
15
+ )
16
+ code: Annotated[
17
+ str,
18
+ Field(
19
+ description='SCREAMING_SNAKE_CASE, such as `ACCESS_DENIED`. A string rather than an\nenum, so a worker release can add codes that readers built against an\nolder ddc still accept.'
20
+ ),
21
+ ]
22
+ message: Annotated[
23
+ str,
24
+ Field(
25
+ description='Safe to show to the user: no credentials and no dataset values.'
26
+ ),
27
+ ]
28
+
29
+
30
+ class ExportTotals(BaseModel):
31
+ model_config = ConfigDict(
32
+ populate_by_name=True,
33
+ )
34
+ bytes: Annotated[int, Field(ge=0)]
35
+ files: Annotated[int, Field(ge=0)]
36
+ rows: Annotated[
37
+ int | None,
38
+ Field(
39
+ description='`None` unless the worker counted the rows of every file.', ge=0
40
+ ),
41
+ ] = None
42
+
43
+
44
+ class ExportedFile(BaseModel):
45
+ model_config = ConfigDict(
46
+ populate_by_name=True,
47
+ )
48
+ bytes: Annotated[int, Field(ge=0)]
49
+ path: Annotated[str, Field(description='The object key in the bucket.')]
50
+ rows: Annotated[
51
+ int | None, Field(description="`None` where the worker can't count rows.", ge=0)
52
+ ] = None
53
+
54
+
55
+ class InputStatus(Enum):
56
+ success = 'SUCCESS'
57
+ failed = 'FAILED'
58
+
59
+
60
+ class BucketInputReceipt(BaseModel):
61
+ model_config = ConfigDict(
62
+ populate_by_name=True,
63
+ )
64
+ errors: list[ExportMessage]
65
+ files: list[ExportedFile]
66
+ leaf_id: Annotated[str, Field(alias='leafId')]
67
+ status: InputStatus
68
+ totals: ExportTotals
69
+ warnings: list[ExportMessage]
70
+
71
+
72
+ class ExportReceiptV01(BaseModel):
73
+ model_config = ConfigDict(
74
+ populate_by_name=True,
75
+ )
76
+ errors: Annotated[
77
+ list[ExportMessage],
78
+ Field(
79
+ description='Problems tied to no single Input, such as credentials rejected before\nany upload.'
80
+ ),
81
+ ]
82
+ export_prefix: Annotated[
83
+ str | None,
84
+ Field(
85
+ alias='exportPrefix',
86
+ description='The export prefix with its date tokens filled; `None` without one.',
87
+ ),
88
+ ] = None
89
+ inputs: list[BucketInputReceipt]
90
+ totals: ExportTotals
91
+ warnings: list[ExportMessage]
92
+ type: Literal['BUCKET']
93
+
94
+
95
+ class ExportReceiptV0(RootModel[ExportReceiptV01]):
96
+ root: ExportReceiptV01
97
+
98
+
99
+ class ExportReceipt1(BaseModel):
100
+ model_config = ConfigDict(
101
+ populate_by_name=True,
102
+ )
103
+ version: Literal['V0']
104
+ errors: Annotated[
105
+ list[ExportMessage],
106
+ Field(
107
+ description='Problems tied to no single Input, such as credentials rejected before\nany upload.'
108
+ ),
109
+ ]
110
+ export_prefix: Annotated[
111
+ str | None,
112
+ Field(
113
+ alias='exportPrefix',
114
+ description='The export prefix with its date tokens filled; `None` without one.',
115
+ ),
116
+ ] = None
117
+ inputs: list[BucketInputReceipt]
118
+ totals: ExportTotals
119
+ warnings: list[ExportMessage]
120
+ type: Literal['BUCKET']
121
+
122
+
123
+ class ExportReceipt(RootModel[ExportReceipt1]):
124
+ root: Annotated[
125
+ ExportReceipt1,
126
+ Field(
127
+ description='A released version never changes: a change is a new version, and readers\nkeep reading every version ever written.',
128
+ title='ExportReceipt',
129
+ ),
130
+ ]
131
+
132
+
133
+ class BucketExportReceiptV0(BaseModel):
134
+ model_config = ConfigDict(
135
+ populate_by_name=True,
136
+ )
137
+ errors: Annotated[
138
+ list[ExportMessage],
139
+ Field(
140
+ description='Problems tied to no single Input, such as credentials rejected before\nany upload.'
141
+ ),
142
+ ]
143
+ export_prefix: Annotated[
144
+ str | None,
145
+ Field(
146
+ alias='exportPrefix',
147
+ description='The export prefix with its date tokens filled; `None` without one.',
148
+ ),
149
+ ] = None
150
+ inputs: list[BucketInputReceipt]
151
+ totals: ExportTotals
152
+ warnings: list[ExportMessage]
@@ -0,0 +1,183 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: export_worker_config.json
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import Annotated, Literal
7
+
8
+ from pydantic import BaseModel, ConfigDict, Field, RootModel
9
+
10
+
11
+ class BucketConnection1(BaseModel):
12
+ model_config = ConfigDict(
13
+ populate_by_name=True,
14
+ )
15
+ bucket: str
16
+ path_prefix: Annotated[str | None, Field(alias='pathPrefix')] = None
17
+ region: str
18
+ type: Literal['s3']
19
+
20
+
21
+ class BucketConnection2(BaseModel):
22
+ model_config = ConfigDict(
23
+ populate_by_name=True,
24
+ )
25
+ account_name: Annotated[str, Field(alias='accountName')]
26
+ container: str
27
+ path_prefix: Annotated[str | None, Field(alias='pathPrefix')] = None
28
+ type: Literal['azureBlob']
29
+
30
+
31
+ class BucketConnection3(BaseModel):
32
+ model_config = ConfigDict(
33
+ populate_by_name=True,
34
+ )
35
+ bucket: str
36
+ path_prefix: Annotated[str | None, Field(alias='pathPrefix')] = None
37
+ type: Literal['gcs']
38
+
39
+
40
+ class BucketConnection(
41
+ RootModel[BucketConnection1 | BucketConnection2 | BucketConnection3]
42
+ ):
43
+ root: Annotated[
44
+ BucketConnection1 | BucketConnection2 | BucketConnection3,
45
+ Field(
46
+ description='Cloud-bucket addressing (no credentials). Provider-structurally paired with\nthe matching [`BucketCredentials`] variant in the confidential half.'
47
+ ),
48
+ ]
49
+
50
+
51
+ class BucketInput(BaseModel):
52
+ model_config = ConfigDict(
53
+ populate_by_name=True,
54
+ )
55
+ destination_directory: Annotated[str, Field(alias='destinationDirectory')]
56
+ leaf_id: Annotated[str, Field(alias='leafId')]
57
+
58
+
59
+ class ExportConnectorKind1(BaseModel):
60
+ model_config = ConfigDict(
61
+ populate_by_name=True,
62
+ )
63
+ connection: BucketConnection
64
+ export_prefix: Annotated[
65
+ str | None,
66
+ Field(
67
+ alias='exportPrefix',
68
+ description='May hold `{yyyy}`, `{mm}`, `{dd}` and `{hh}`, which the worker fills in\nUTC when a run starts.',
69
+ ),
70
+ ] = None
71
+ inputs: list[BucketInput]
72
+ type: Literal['bucket']
73
+
74
+
75
+ class ExportConnectorKind(RootModel[ExportConnectorKind1]):
76
+ root: ExportConnectorKind1
77
+
78
+
79
+ class ExportParams1(BaseModel):
80
+ model_config = ConfigDict(
81
+ populate_by_name=True,
82
+ )
83
+ type: Literal['bucket']
84
+
85
+
86
+ class ExportParams(RootModel[ExportParams1]):
87
+ root: ExportParams1
88
+
89
+
90
+ class ProvisionedPolicy(BaseModel):
91
+ model_config = ConfigDict(
92
+ populate_by_name=True,
93
+ )
94
+ leaf_id: Annotated[str, Field(alias='leafId')]
95
+ policy_id: Annotated[str, Field(alias='policyId')]
96
+ provisioned_at: Annotated[
97
+ str, Field(alias='provisionedAt', description='RFC 3339.')
98
+ ]
99
+ provisioned_by_email: Annotated[
100
+ str,
101
+ Field(
102
+ alias='provisionedByEmail',
103
+ description='Whose READ on the dataset every run re-checks.',
104
+ ),
105
+ ]
106
+
107
+
108
+ class ValidateConnectionParams1(BaseModel):
109
+ model_config = ConfigDict(
110
+ populate_by_name=True,
111
+ )
112
+ type: Literal['bucket']
113
+
114
+
115
+ class ValidateConnectionParams(RootModel[ValidateConnectionParams1]):
116
+ root: ValidateConnectionParams1
117
+
118
+
119
+ class BucketExportConfig(BaseModel):
120
+ model_config = ConfigDict(
121
+ populate_by_name=True,
122
+ )
123
+ connection: BucketConnection
124
+ export_prefix: Annotated[
125
+ str | None,
126
+ Field(
127
+ alias='exportPrefix',
128
+ description='May hold `{yyyy}`, `{mm}`, `{dd}` and `{hh}`, which the worker fills in\nUTC when a run starts.',
129
+ ),
130
+ ] = None
131
+ inputs: list[BucketInput]
132
+
133
+
134
+ class ValidateConnection(BaseModel):
135
+ model_config = ConfigDict(
136
+ populate_by_name=True,
137
+ )
138
+ params: ValidateConnectionParams
139
+
140
+
141
+ class ExportConnectorDcrComputeAction1(BaseModel):
142
+ model_config = ConfigDict(
143
+ extra='forbid',
144
+ populate_by_name=True,
145
+ )
146
+ validate_connection: Annotated[
147
+ ValidateConnection, Field(alias='validateConnection')
148
+ ]
149
+
150
+
151
+ class Export(BaseModel):
152
+ model_config = ConfigDict(
153
+ populate_by_name=True,
154
+ )
155
+ params: ExportParams
156
+
157
+
158
+ class ExportConnectorDcrComputeAction2(BaseModel):
159
+ model_config = ConfigDict(
160
+ extra='forbid',
161
+ populate_by_name=True,
162
+ )
163
+ export: Export
164
+
165
+
166
+ class ExportConnectorDcrComputeAction(
167
+ RootModel[ExportConnectorDcrComputeAction1 | ExportConnectorDcrComputeAction2]
168
+ ):
169
+ root: Annotated[
170
+ ExportConnectorDcrComputeAction1 | ExportConnectorDcrComputeAction2,
171
+ Field(
172
+ description='Compute actions for an Export Connector DCR, started through `POST /jobs`.'
173
+ ),
174
+ ]
175
+
176
+
177
+ class ExportWorkerConfig(BaseModel):
178
+ model_config = ConfigDict(
179
+ populate_by_name=True,
180
+ )
181
+ action: ExportConnectorDcrComputeAction
182
+ connector: ExportConnectorKind
183
+ policies: list[ProvisionedPolicy]