meltano-state-backend-snowflake 0.1.0__py3-none-any.whl → 0.2.0__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.
- meltano_state_backend_snowflake/backend.py +25 -48
- {meltano_state_backend_snowflake-0.1.0.dist-info → meltano_state_backend_snowflake-0.2.0.dist-info}/METADATA +24 -13
- meltano_state_backend_snowflake-0.2.0.dist-info/RECORD +8 -0
- {meltano_state_backend_snowflake-0.1.0.dist-info → meltano_state_backend_snowflake-0.2.0.dist-info}/WHEEL +1 -1
- meltano_state_backend_snowflake-0.1.0.dist-info/RECORD +0 -8
- {meltano_state_backend_snowflake-0.1.0.dist-info → meltano_state_backend_snowflake-0.2.0.dist-info}/entry_points.txt +0 -0
- {meltano_state_backend_snowflake-0.1.0.dist-info → meltano_state_backend_snowflake-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -31,7 +31,7 @@ SNOWFLAKE_ACCOUNT = SettingDefinition(
|
|
|
31
31
|
name="state_backend.snowflake.account",
|
|
32
32
|
label="Snowflake Account",
|
|
33
33
|
description="Snowflake account identifier",
|
|
34
|
-
kind=SettingKind.STRING,
|
|
34
|
+
kind=SettingKind.STRING, # ty: ignore[invalid-argument-type]
|
|
35
35
|
env_specific=True,
|
|
36
36
|
)
|
|
37
37
|
|
|
@@ -39,7 +39,7 @@ SNOWFLAKE_USER = SettingDefinition(
|
|
|
39
39
|
name="state_backend.snowflake.user",
|
|
40
40
|
label="Snowflake User",
|
|
41
41
|
description="Snowflake username",
|
|
42
|
-
kind=SettingKind.STRING,
|
|
42
|
+
kind=SettingKind.STRING, # ty: ignore[invalid-argument-type]
|
|
43
43
|
env_specific=True,
|
|
44
44
|
)
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ SNOWFLAKE_PASSWORD = SettingDefinition(
|
|
|
47
47
|
name="state_backend.snowflake.password",
|
|
48
48
|
label="Snowflake Password",
|
|
49
49
|
description="Snowflake password",
|
|
50
|
-
kind=SettingKind.STRING,
|
|
50
|
+
kind=SettingKind.STRING, # ty: ignore[invalid-argument-type]
|
|
51
51
|
sensitive=True,
|
|
52
52
|
env_specific=True,
|
|
53
53
|
)
|
|
@@ -56,7 +56,7 @@ SNOWFLAKE_WAREHOUSE = SettingDefinition(
|
|
|
56
56
|
name="state_backend.snowflake.warehouse",
|
|
57
57
|
label="Snowflake Warehouse",
|
|
58
58
|
description="Snowflake compute warehouse",
|
|
59
|
-
kind=SettingKind.STRING,
|
|
59
|
+
kind=SettingKind.STRING, # ty: ignore[invalid-argument-type]
|
|
60
60
|
env_specific=True,
|
|
61
61
|
)
|
|
62
62
|
|
|
@@ -64,7 +64,7 @@ SNOWFLAKE_DATABASE = SettingDefinition(
|
|
|
64
64
|
name="state_backend.snowflake.database",
|
|
65
65
|
label="Snowflake Database",
|
|
66
66
|
description="Snowflake database name",
|
|
67
|
-
kind=SettingKind.STRING,
|
|
67
|
+
kind=SettingKind.STRING, # ty: ignore[invalid-argument-type]
|
|
68
68
|
env_specific=True,
|
|
69
69
|
)
|
|
70
70
|
|
|
@@ -72,8 +72,8 @@ SNOWFLAKE_SCHEMA = SettingDefinition(
|
|
|
72
72
|
name="state_backend.snowflake.schema",
|
|
73
73
|
label="Snowflake Schema",
|
|
74
74
|
description="Snowflake schema name",
|
|
75
|
-
kind=SettingKind.STRING,
|
|
76
|
-
|
|
75
|
+
kind=SettingKind.STRING, # ty: ignore[invalid-argument-type]
|
|
76
|
+
value="PUBLIC",
|
|
77
77
|
env_specific=True,
|
|
78
78
|
)
|
|
79
79
|
|
|
@@ -81,7 +81,7 @@ SNOWFLAKE_ROLE = SettingDefinition(
|
|
|
81
81
|
name="state_backend.snowflake.role",
|
|
82
82
|
label="Snowflake Role",
|
|
83
83
|
description="Snowflake role to use",
|
|
84
|
-
kind=SettingKind.STRING,
|
|
84
|
+
kind=SettingKind.STRING, # ty: ignore[invalid-argument-type]
|
|
85
85
|
env_specific=True,
|
|
86
86
|
)
|
|
87
87
|
|
|
@@ -186,9 +186,7 @@ class SnowflakeStateStoreManager(StateStoreManager):
|
|
|
186
186
|
f"""
|
|
187
187
|
CREATE TABLE IF NOT EXISTS {self.database}.{self.schema}.{self.table_name} (
|
|
188
188
|
state_id VARCHAR PRIMARY KEY,
|
|
189
|
-
|
|
190
|
-
completed_state VARIANT,
|
|
191
|
-
updated_at TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
|
|
189
|
+
state VARIANT
|
|
192
190
|
)
|
|
193
191
|
""",
|
|
194
192
|
)
|
|
@@ -211,26 +209,19 @@ class SnowflakeStateStoreManager(StateStoreManager):
|
|
|
211
209
|
state: the state to set.
|
|
212
210
|
|
|
213
211
|
"""
|
|
214
|
-
partial_json = json.dumps(state.partial_state) if state.partial_state else None
|
|
215
|
-
completed_json = json.dumps(state.completed_state) if state.completed_state else None
|
|
216
|
-
|
|
217
212
|
with self.connection.cursor() as cursor:
|
|
218
213
|
cursor.execute(
|
|
219
214
|
f"""
|
|
220
215
|
MERGE INTO {self.database}.{self.schema}.{self.table_name} AS target
|
|
221
|
-
USING (SELECT %s AS state_id, PARSE_JSON(%s) AS
|
|
222
|
-
PARSE_JSON(%s) AS completed_state) AS source
|
|
216
|
+
USING (SELECT %s AS state_id, PARSE_JSON(%s) AS state) AS source
|
|
223
217
|
ON target.state_id = source.state_id
|
|
224
218
|
WHEN MATCHED THEN
|
|
225
|
-
UPDATE SET
|
|
226
|
-
partial_state = source.partial_state,
|
|
227
|
-
completed_state = source.completed_state,
|
|
228
|
-
updated_at = CURRENT_TIMESTAMP()
|
|
219
|
+
UPDATE SET state = source.state
|
|
229
220
|
WHEN NOT MATCHED THEN
|
|
230
|
-
INSERT (state_id,
|
|
231
|
-
VALUES (source.state_id, source.
|
|
221
|
+
INSERT (state_id, state)
|
|
222
|
+
VALUES (source.state_id, source.state)
|
|
232
223
|
""", # noqa: S608
|
|
233
|
-
(state.state_id,
|
|
224
|
+
(state.state_id, state.json()),
|
|
234
225
|
)
|
|
235
226
|
|
|
236
227
|
def get(self, state_id: str) -> MeltanoState | None:
|
|
@@ -246,7 +237,7 @@ class SnowflakeStateStoreManager(StateStoreManager):
|
|
|
246
237
|
with self.connection.cursor() as cursor:
|
|
247
238
|
cursor.execute(
|
|
248
239
|
f"""
|
|
249
|
-
SELECT
|
|
240
|
+
SELECT state
|
|
250
241
|
FROM {self.database}.{self.schema}.{self.table_name}
|
|
251
242
|
WHERE state_id = %s
|
|
252
243
|
""", # noqa: S608
|
|
@@ -257,30 +248,16 @@ class SnowflakeStateStoreManager(StateStoreManager):
|
|
|
257
248
|
if not row:
|
|
258
249
|
return None
|
|
259
250
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
elif isinstance(partial_state, str):
|
|
271
|
-
partial_state = json.loads(partial_state)
|
|
272
|
-
|
|
273
|
-
if completed_state is None:
|
|
274
|
-
completed_state = {}
|
|
275
|
-
# Parse JSON string if Snowflake returns string instead of dict
|
|
276
|
-
elif isinstance(completed_state, str):
|
|
277
|
-
completed_state = json.loads(completed_state)
|
|
278
|
-
|
|
279
|
-
return MeltanoState(
|
|
280
|
-
state_id=state_id,
|
|
281
|
-
partial_state=partial_state,
|
|
282
|
-
completed_state=completed_state,
|
|
283
|
-
)
|
|
251
|
+
state = row[0]
|
|
252
|
+
|
|
253
|
+
if state is None:
|
|
254
|
+
return MeltanoState(state_id=state_id)
|
|
255
|
+
|
|
256
|
+
# VARIANT columns may come back as a dict or a JSON string
|
|
257
|
+
if isinstance(state, dict):
|
|
258
|
+
state = json.dumps(state)
|
|
259
|
+
|
|
260
|
+
return MeltanoState.from_json(state_id, state)
|
|
284
261
|
|
|
285
262
|
def delete(self, state_id: str) -> None:
|
|
286
263
|
"""Delete state for the given state_id.
|
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meltano-state-backend-snowflake
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Meltano State Backend for Snowflake
|
|
5
|
+
Project-URL: Documentation, https://github.com/meltano/meltano-state-backend-snowflake
|
|
6
|
+
Project-URL: Source, https://github.com/meltano/meltano-state-backend-snowflake
|
|
7
|
+
Project-URL: Issues, https://github.com/meltano/meltano-state-backend-snowflake/issues
|
|
5
8
|
Author-email: Taylor Murphy <taylor@arch.dev>
|
|
6
9
|
License-Expression: MIT
|
|
7
10
|
License-File: LICENSE
|
|
8
11
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
10
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
11
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Python: >=3.10
|
|
15
18
|
Requires-Dist: meltano>=3.7
|
|
16
|
-
Requires-Dist: snowflake-connector-python<4
|
|
19
|
+
Requires-Dist: snowflake-connector-python<5,>=4
|
|
17
20
|
Description-Content-Type: text/markdown
|
|
18
21
|
|
|
19
22
|
# `meltano-state-backend-snowflake`
|
|
20
23
|
|
|
24
|
+
<!-- Display these if and when we publish to PyPI. -->
|
|
25
|
+
|
|
26
|
+
<!--
|
|
21
27
|
[](https://pypi.org/project/meltano-state-backend-snowflake)
|
|
22
|
-
[](https://pypi.org/project/meltano-state-backend-snowflake)
|
|
28
|
+
[](https://pypi.org/project/meltano-state-backend-snowflake) -->
|
|
23
29
|
|
|
24
|
-
This is a [Meltano]
|
|
30
|
+
This is a [Meltano] extension that provides a [Snowflake] [state backend][state-backend].
|
|
25
31
|
|
|
26
32
|
## Installation
|
|
27
33
|
|
|
@@ -32,14 +38,14 @@ This package needs to be installed in the same Python environment as Meltano.
|
|
|
32
38
|
#### With [uv]
|
|
33
39
|
|
|
34
40
|
```bash
|
|
35
|
-
uv tool install --with meltano-state-backend-snowflake meltano
|
|
41
|
+
uv tool install --with git+https://github.com/meltano/meltano-state-backend-snowflake.git meltano
|
|
36
42
|
```
|
|
37
43
|
|
|
38
44
|
#### With [pipx]
|
|
39
45
|
|
|
40
46
|
```bash
|
|
41
47
|
pipx install meltano
|
|
42
|
-
pipx inject meltano
|
|
48
|
+
pipx inject meltano git+https://github.com/meltano/meltano-state-backend-snowflake.git
|
|
43
49
|
```
|
|
44
50
|
|
|
45
51
|
## Configuration
|
|
@@ -47,6 +53,7 @@ pipx inject meltano 'meltano-state-backend-snowflake
|
|
|
47
53
|
To store state in Snowflake, set the `state_backend.uri` setting to `snowflake://<user>:<password>@<account>/<database>/<schema>`.
|
|
48
54
|
|
|
49
55
|
State will be stored in two tables that Meltano will create automatically:
|
|
56
|
+
|
|
50
57
|
- `meltano_state` - Stores the actual state data
|
|
51
58
|
- `meltano_state_locks` - Manages concurrency locks
|
|
52
59
|
|
|
@@ -88,6 +95,7 @@ state_backend:
|
|
|
88
95
|
#### Security Considerations
|
|
89
96
|
|
|
90
97
|
When storing credentials:
|
|
98
|
+
|
|
91
99
|
- Use environment variables for sensitive values in production
|
|
92
100
|
- Consider using Snowflake key-pair authentication (future enhancement)
|
|
93
101
|
- Ensure the user has CREATE TABLE, INSERT, UPDATE, DELETE, and SELECT privileges
|
|
@@ -96,8 +104,8 @@ Example using environment variables:
|
|
|
96
104
|
|
|
97
105
|
```bash
|
|
98
106
|
export MELTANO_STATE_BACKEND_SNOWFLAKE_PASSWORD='my_secure_password'
|
|
99
|
-
meltano config meltano
|
|
100
|
-
meltano config meltano
|
|
107
|
+
meltano config set meltano state_backend.uri 'snowflake://my_user@my_account/my_database'
|
|
108
|
+
meltano config set meltano state_backend.snowflake.warehouse 'my_warehouse'
|
|
101
109
|
```
|
|
102
110
|
|
|
103
111
|
## Development
|
|
@@ -113,17 +121,20 @@ uv sync
|
|
|
113
121
|
Run all tests, type checks, linting, and coverage:
|
|
114
122
|
|
|
115
123
|
```bash
|
|
116
|
-
uvx
|
|
124
|
+
uvx --with tox-uv tox run-parallel
|
|
117
125
|
```
|
|
118
126
|
|
|
119
127
|
### Bump the version
|
|
120
128
|
|
|
129
|
+
Using the [GitHub CLI][gh]:
|
|
130
|
+
|
|
121
131
|
```bash
|
|
122
|
-
|
|
132
|
+
gh release create v<new-version>
|
|
123
133
|
```
|
|
124
134
|
|
|
135
|
+
[gh]: https://cli.github.com/
|
|
125
136
|
[meltano]: https://meltano.com
|
|
137
|
+
[pipx]: https://github.com/pypa/pipx
|
|
126
138
|
[snowflake]: https://www.snowflake.com/
|
|
127
139
|
[state-backend]: https://docs.meltano.com/concepts/state_backends
|
|
128
|
-
[pipx]: https://github.com/pypa/pipx
|
|
129
140
|
[uv]: https://docs.astral.sh/uv
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
meltano_state_backend_snowflake/__init__.py,sha256=VKrpkX_1iA77T_Akw03QYSwhEouiB6WCwk8ni2SAexM,43
|
|
2
|
+
meltano_state_backend_snowflake/backend.py,sha256=-yrVSRmpipTRhlIL5UlQy7zy2-2S4Ysdw0OJcTxQEsY,12578
|
|
3
|
+
meltano_state_backend_snowflake/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
meltano_state_backend_snowflake-0.2.0.dist-info/METADATA,sha256=tj3GzE62XoK_b9TwvSXi-3QiUTWZQXJSdO0l7_UCRxY,4384
|
|
5
|
+
meltano_state_backend_snowflake-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
meltano_state_backend_snowflake-0.2.0.dist-info/entry_points.txt,sha256=jZRNuruL8DV7LQshmT9rLCjFWFU1RFDiTKMjSdb1Gak,664
|
|
7
|
+
meltano_state_backend_snowflake-0.2.0.dist-info/licenses/LICENSE,sha256=-5_wfLmGpH1fzTKoBkjWeEnK7cV60Cs0c6Ap3bE9n1U,1064
|
|
8
|
+
meltano_state_backend_snowflake-0.2.0.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
meltano_state_backend_snowflake/__init__.py,sha256=VKrpkX_1iA77T_Akw03QYSwhEouiB6WCwk8ni2SAexM,43
|
|
2
|
-
meltano_state_backend_snowflake/backend.py,sha256=f_dVCUG0YYy9z1Rl_oXEgDfBOgTfekTsRZsMmXddk9s,13639
|
|
3
|
-
meltano_state_backend_snowflake/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
meltano_state_backend_snowflake-0.1.0.dist-info/METADATA,sha256=U5abP9PQjKPmB4b0Vo0wgJ6YLit_mE-u-jtDC2JOueY,3944
|
|
5
|
-
meltano_state_backend_snowflake-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
meltano_state_backend_snowflake-0.1.0.dist-info/entry_points.txt,sha256=jZRNuruL8DV7LQshmT9rLCjFWFU1RFDiTKMjSdb1Gak,664
|
|
7
|
-
meltano_state_backend_snowflake-0.1.0.dist-info/licenses/LICENSE,sha256=-5_wfLmGpH1fzTKoBkjWeEnK7cV60Cs0c6Ap3bE9n1U,1064
|
|
8
|
-
meltano_state_backend_snowflake-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|