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.
@@ -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
- default="PUBLIC",
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
- partial_state VARIANT,
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 partial_state,
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, partial_state, completed_state)
231
- VALUES (source.state_id, source.partial_state, source.completed_state)
221
+ INSERT (state_id, state)
222
+ VALUES (source.state_id, source.state)
232
223
  """, # noqa: S608
233
- (state.state_id, partial_json, completed_json),
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 partial_state, completed_state
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
- # Snowflake returns None for NULL VARIANT columns
261
- # but MeltanoState expects empty dicts
262
- # Additionally, VARIANT columns might return JSON strings that need parsing
263
- partial_state = row[0]
264
- completed_state = row[1]
265
-
266
- # Handle None values
267
- if partial_state is None:
268
- partial_state = {}
269
- # Parse JSON string if Snowflake returns string instead of dict
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.1.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
- Requires-Python: >=3.9
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,>=3
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
  [![PyPI version](https://img.shields.io/pypi/v/meltano-state-backend-snowflake.svg?logo=pypi&logoColor=FFE873&color=blue)](https://pypi.org/project/meltano-state-backend-snowflake)
22
- [![Python versions](https://img.shields.io/pypi/pyversions/meltano-state-backend-snowflake.svg?logo=python&logoColor=FFE873)](https://pypi.org/project/meltano-state-backend-snowflake)
28
+ [![Python versions](https://img.shields.io/pypi/pyversions/meltano-state-backend-snowflake.svg?logo=python&logoColor=FFE873)](https://pypi.org/project/meltano-state-backend-snowflake) -->
23
29
 
24
- This is a [Meltano][meltano] extension that provides a [Snowflake][snowflake] [state backend][state-backend].
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 'meltano-state-backend-snowflake
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 set state_backend.uri 'snowflake://my_user@my_account/my_database'
100
- meltano config meltano set state_backend.snowflake.warehouse 'my_warehouse'
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 -with tox-uv tox run-parallel
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
- uv version --bump <type>
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,