snowflake-cli 3.3.0__py3-none-any.whl → 3.4.1__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.
Files changed (77) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/__main__.py +2 -2
  3. snowflake/cli/_app/cli_app.py +224 -192
  4. snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +1 -27
  5. snowflake/cli/_plugins/cortex/commands.py +2 -4
  6. snowflake/cli/_plugins/git/manager.py +1 -1
  7. snowflake/cli/_plugins/nativeapp/artifacts.py +6 -624
  8. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  9. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  10. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +1 -3
  11. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
  12. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +2 -2
  13. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +2 -2
  14. snowflake/cli/_plugins/nativeapp/commands.py +21 -19
  15. snowflake/cli/_plugins/nativeapp/entities/application.py +16 -19
  16. snowflake/cli/_plugins/nativeapp/entities/application_package.py +142 -55
  17. snowflake/cli/_plugins/nativeapp/release_channel/commands.py +37 -3
  18. snowflake/cli/_plugins/nativeapp/release_directive/commands.py +80 -2
  19. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +224 -44
  20. snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +2 -2
  21. snowflake/cli/_plugins/nativeapp/version/commands.py +1 -1
  22. snowflake/cli/_plugins/notebook/commands.py +55 -2
  23. snowflake/cli/_plugins/notebook/exceptions.py +1 -1
  24. snowflake/cli/_plugins/notebook/manager.py +3 -3
  25. snowflake/cli/_plugins/notebook/notebook_entity.py +120 -0
  26. snowflake/cli/_plugins/notebook/notebook_entity_model.py +42 -0
  27. snowflake/cli/_plugins/notebook/notebook_project_paths.py +15 -0
  28. snowflake/cli/_plugins/notebook/types.py +3 -0
  29. snowflake/cli/_plugins/snowpark/commands.py +48 -30
  30. snowflake/cli/_plugins/snowpark/common.py +47 -2
  31. snowflake/cli/_plugins/snowpark/snowpark_entity.py +38 -25
  32. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +18 -30
  33. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +156 -23
  34. snowflake/cli/_plugins/snowpark/zipper.py +33 -1
  35. snowflake/cli/_plugins/spcs/services/commands.py +0 -3
  36. snowflake/cli/_plugins/stage/commands.py +2 -1
  37. snowflake/cli/_plugins/stage/diff.py +60 -39
  38. snowflake/cli/_plugins/stage/manager.py +24 -11
  39. snowflake/cli/_plugins/stage/utils.py +1 -1
  40. snowflake/cli/_plugins/streamlit/commands.py +10 -1
  41. snowflake/cli/_plugins/streamlit/manager.py +62 -21
  42. snowflake/cli/_plugins/streamlit/streamlit_entity.py +20 -41
  43. snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +14 -24
  44. snowflake/cli/_plugins/streamlit/streamlit_project_paths.py +30 -0
  45. snowflake/cli/_plugins/workspace/commands.py +3 -3
  46. snowflake/cli/_plugins/workspace/manager.py +1 -1
  47. snowflake/cli/api/artifacts/__init__.py +13 -0
  48. snowflake/cli/api/artifacts/bundle_map.py +500 -0
  49. snowflake/cli/api/artifacts/common.py +78 -0
  50. snowflake/cli/api/artifacts/utils.py +82 -0
  51. snowflake/cli/api/cli_global_context.py +14 -1
  52. snowflake/cli/api/commands/flags.py +10 -4
  53. snowflake/cli/api/commands/utils.py +28 -2
  54. snowflake/cli/api/constants.py +1 -0
  55. snowflake/cli/api/entities/common.py +14 -32
  56. snowflake/cli/api/entities/resolver.py +160 -0
  57. snowflake/cli/api/entities/utils.py +56 -15
  58. snowflake/cli/api/errno.py +3 -0
  59. snowflake/cli/api/feature_flags.py +1 -2
  60. snowflake/cli/api/project/definition_conversion.py +3 -2
  61. snowflake/cli/api/project/project_paths.py +28 -0
  62. snowflake/cli/api/project/schemas/entities/common.py +130 -1
  63. snowflake/cli/api/project/schemas/entities/entities.py +4 -0
  64. snowflake/cli/api/project/schemas/project_definition.py +27 -0
  65. snowflake/cli/api/project/schemas/updatable_model.py +2 -2
  66. snowflake/cli/api/project/schemas/v1/native_app/native_app.py +5 -7
  67. snowflake/cli/api/secure_path.py +6 -0
  68. snowflake/cli/api/sql_execution.py +5 -1
  69. snowflake/cli/api/stage_path.py +7 -2
  70. snowflake/cli/api/utils/graph.py +3 -0
  71. snowflake/cli/api/utils/path_utils.py +24 -0
  72. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/METADATA +8 -9
  73. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/RECORD +76 -67
  74. snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
  75. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/WHEEL +0 -0
  76. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/entry_points.txt +0 -0
  77. {snowflake_cli-3.3.0.dist-info → snowflake_cli-3.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -15,9 +15,10 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  from abc import ABC
18
- from typing import Dict, Generic, List, Optional, TypeVar, Union
18
+ from typing import Any, Dict, Generic, List, Optional, TypeVar, Union
19
19
 
20
20
  from pydantic import Field, PrivateAttr, field_validator
21
+ from pydantic_core.core_schema import ValidationInfo
21
22
  from snowflake.cli.api.identifiers import FQN
22
23
  from snowflake.cli.api.project.schemas.updatable_model import (
23
24
  IdentifierField,
@@ -61,6 +62,15 @@ class MetaField(UpdatableModel):
61
62
  default=None,
62
63
  )
63
64
 
65
+ depends_on: Optional[List[str]] = Field(
66
+ title="Entities that need to be deployed before this one", default_factory=list
67
+ )
68
+
69
+ action_arguments: Optional[Dict[str, Dict[str, Union[int, bool, str]]]] = Field(
70
+ title="Arguments that will be used, when this entity is called as a dependency of other entity",
71
+ default_factory=dict,
72
+ )
73
+
64
74
  @field_validator("use_mixins", mode="before")
65
75
  @classmethod
66
76
  def ensure_use_mixins_is_a_list(
@@ -70,6 +80,35 @@ class MetaField(UpdatableModel):
70
80
  return [mixins]
71
81
  return mixins
72
82
 
83
+ @field_validator("action_arguments", mode="before")
84
+ @classmethod
85
+ def arguments_validator(cls, arguments: Dict, info: ValidationInfo) -> Dict:
86
+ duplicated_run = (
87
+ info.context.get("is_duplicated_run", False) if info.context else False
88
+ )
89
+ if not duplicated_run:
90
+ for argument_dict in arguments.values():
91
+ for k, v in argument_dict.items():
92
+ argument_dict[k] = cls._cast_value(v)
93
+
94
+ return arguments
95
+
96
+ @staticmethod
97
+ def _cast_value(value: str) -> Union[int, bool, str]:
98
+ if value.lower() in ["true", "false"]:
99
+ return value.lower() == "true"
100
+
101
+ try:
102
+ return int(value)
103
+ except ValueError:
104
+ return value
105
+
106
+ def __eq__(self, other):
107
+ return self.entity_id == other.entity_id
108
+
109
+ def __hash__(self):
110
+ return hash(self.entity_id)
111
+
73
112
 
74
113
  class Identifier(UpdatableModel):
75
114
  name: str = Field(title="Entity name")
@@ -141,6 +180,23 @@ class ImportsBaseModel:
141
180
  return f"IMPORTS = ({imports})"
142
181
 
143
182
 
183
+ class Grant(UpdatableModel):
184
+ privilege: str = Field(title="Required privileges")
185
+ role: str = Field(title="Role to which the privileges will be granted")
186
+
187
+ def get_grant_sql(self, entity_model: EntityModelBase) -> str:
188
+ return f"GRANT {self.privilege} ON {entity_model.get_type().upper()} {entity_model.fqn.sql_identifier} TO ROLE {self.role}"
189
+
190
+
191
+ class GrantBaseModel(UpdatableModel):
192
+ grants: Optional[List[Grant]] = Field(title="List of grants", default=None)
193
+
194
+ def get_grant_sqls(self) -> list[str]:
195
+ return (
196
+ [grant.get_grant_sql(self) for grant in self.grants] if self.grants else []
197
+ )
198
+
199
+
144
200
  class ExternalAccessBaseModel:
145
201
  external_access_integrations: Optional[List[str]] = Field(
146
202
  title="Names of external access integrations needed for this entity to access external networks",
@@ -162,3 +218,76 @@ class ExternalAccessBaseModel:
162
218
  return None
163
219
  secrets = ", ".join(f"'{key}'={value}" for key, value in self.secrets.items())
164
220
  return f"secrets=({secrets})"
221
+
222
+
223
+ class ProcessorMapping(UpdatableModel):
224
+ name: str = Field(
225
+ title="Name of a processor to invoke on a collection of artifacts."
226
+ )
227
+ properties: Optional[Dict[str, Any]] = Field(
228
+ title="A set of key-value pairs used to configure the output of the processor. Consult a specific processor's documentation for more details on the supported properties.",
229
+ default=None,
230
+ )
231
+
232
+
233
+ class PathMapping(UpdatableModel):
234
+ src: str = Field(
235
+ title="Source path or glob pattern (relative to project root)", default=None
236
+ )
237
+
238
+ dest: Optional[str] = Field(
239
+ title="Destination path on stage",
240
+ description="Paths are relative to stage root; paths ending with a slash indicate that the destination is a directory which source files should be copied into.",
241
+ default=None,
242
+ )
243
+
244
+ processors: Optional[List[Union[str, ProcessorMapping]]] = Field(
245
+ title="List of processors to apply to matching source files during bundling.",
246
+ default=[],
247
+ )
248
+
249
+ @field_validator("processors")
250
+ @classmethod
251
+ def transform_processors(
252
+ cls, input_values: Optional[List[Union[str, Dict, ProcessorMapping]]]
253
+ ) -> List[ProcessorMapping]:
254
+ if input_values is None:
255
+ return []
256
+
257
+ transformed_processors: List[ProcessorMapping] = []
258
+ for input_processor in input_values:
259
+ if isinstance(input_processor, str):
260
+ transformed_processors.append(ProcessorMapping(name=input_processor))
261
+ elif isinstance(input_processor, Dict):
262
+ transformed_processors.append(ProcessorMapping(**input_processor))
263
+ else:
264
+ transformed_processors.append(input_processor)
265
+ return transformed_processors
266
+
267
+
268
+ Artifacts = List[Union[PathMapping, str]]
269
+
270
+
271
+ class EntityModelBaseWithArtifacts(EntityModelBase):
272
+ artifacts: Artifacts = Field(
273
+ title="List of paths or file source/destination pairs to add to the deploy root",
274
+ )
275
+ deploy_root: Optional[str] = Field(
276
+ title="Folder at the root of your project where the build step copies the artifacts",
277
+ default="output/deploy/",
278
+ )
279
+
280
+ @field_validator("artifacts")
281
+ @classmethod
282
+ def transform_artifacts(cls, orig_artifacts: Artifacts) -> List[PathMapping]:
283
+ transformed_artifacts: List[PathMapping] = []
284
+ if orig_artifacts is None:
285
+ return transformed_artifacts
286
+
287
+ for artifact in orig_artifacts:
288
+ if isinstance(artifact, PathMapping):
289
+ transformed_artifacts.append(artifact)
290
+ else:
291
+ transformed_artifacts.append(PathMapping(src=artifact))
292
+
293
+ return transformed_artifacts
@@ -24,6 +24,8 @@ from snowflake.cli._plugins.nativeapp.entities.application_package import (
24
24
  ApplicationPackageEntity,
25
25
  ApplicationPackageEntityModel,
26
26
  )
27
+ from snowflake.cli._plugins.notebook.notebook_entity import NotebookEntity
28
+ from snowflake.cli._plugins.notebook.notebook_entity_model import NotebookEntityModel
27
29
  from snowflake.cli._plugins.snowpark.snowpark_entity import (
28
30
  FunctionEntity,
29
31
  ProcedureEntity,
@@ -43,6 +45,7 @@ Entity = Union[
43
45
  StreamlitEntity,
44
46
  ProcedureEntity,
45
47
  FunctionEntity,
48
+ NotebookEntity,
46
49
  ]
47
50
  EntityModel = Union[
48
51
  ApplicationEntityModel,
@@ -50,6 +53,7 @@ EntityModel = Union[
50
53
  StreamlitEntityModel,
51
54
  FunctionEntityModel,
52
55
  ProcedureEntityModel,
56
+ NotebookEntityModel,
53
57
  ]
54
58
 
55
59
  ALL_ENTITIES: List[Entity] = [*get_args(Entity)]
@@ -14,6 +14,7 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ from collections import defaultdict
17
18
  from dataclasses import dataclass
18
19
  from types import UnionType
19
20
  from typing import Any, Dict, List, Optional, Union, get_args, get_origin
@@ -262,6 +263,21 @@ class DefinitionV20(_ProjectDefinitionBase):
262
263
  data = cls._merge_data(data, entity)
263
264
  return data
264
265
 
266
+ @model_validator(mode="after")
267
+ def validate_dependencies(self):
268
+ """
269
+ Checks if entities listed in depends_on section exist in the project
270
+ """
271
+ missing_dependencies = defaultdict(list)
272
+ for entity_id, entity in self.entities.items():
273
+ if entity.meta:
274
+ for dependency in entity.meta.depends_on:
275
+ if dependency not in self.entities:
276
+ missing_dependencies[entity_id].append(dependency)
277
+
278
+ if missing_dependencies:
279
+ raise ValueError(_get_missing_dependencies_message(missing_dependencies))
280
+
265
281
  @classmethod
266
282
  def _merge_data(
267
283
  cls,
@@ -354,3 +370,14 @@ def _unique_extend(list_a: List, list_b: List) -> List:
354
370
  if all(item != x for x in list_a):
355
371
  new_list.append(item)
356
372
  return new_list
373
+
374
+
375
+ def _get_missing_dependencies_message(
376
+ missing_dependencies: Dict[str, List[str]]
377
+ ) -> str:
378
+ missing_dependencies_message = []
379
+ for entity_id, dependencies in missing_dependencies.items():
380
+ missing_dependencies_message.append(
381
+ f"\n Entity {entity_id} depends on non-existing entities: {', '.join(dependencies)}"
382
+ )
383
+ return "".join(missing_dependencies_message)
@@ -122,10 +122,10 @@ class UpdatableModel(BaseModel):
122
122
  class_dict = class_.__dict__
123
123
  field_annotations.update(class_dict.get("__annotations__", {}))
124
124
 
125
- if "model_fields" in class_dict:
125
+ if "model_fields" in class_dict and class_.model_fields:
126
126
  # This means the class dict has already been processed by Pydantic
127
127
  # All fields should properly be populated in model_fields
128
- field_values.update(class_dict["model_fields"])
128
+ field_values.update(class_.model_fields)
129
129
  else:
130
130
  # If Pydantic did not process this class yet, get the values from class_dict directly
131
131
  field_values.update(class_dict)
@@ -15,16 +15,16 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  import re
18
- from typing import List, Optional, Union
18
+ from typing import List, Optional
19
19
 
20
20
  from pydantic import Field, field_validator
21
+ from snowflake.cli.api.project.schemas.entities.common import Artifacts, PathMapping
21
22
  from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
22
23
  from snowflake.cli.api.project.schemas.v1.native_app.application import (
23
24
  Application,
24
25
  ApplicationV11,
25
26
  )
26
27
  from snowflake.cli.api.project.schemas.v1.native_app.package import Package, PackageV11
27
- from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
28
28
  from snowflake.cli.api.project.util import (
29
29
  SCHEMA_AND_NAME,
30
30
  )
@@ -34,7 +34,7 @@ class NativeApp(UpdatableModel):
34
34
  name: str = Field(
35
35
  title="Project identifier",
36
36
  )
37
- artifacts: List[Union[PathMapping, str]] = Field(
37
+ artifacts: Artifacts = Field(
38
38
  title="List of file source and destination pairs to add to the deploy root",
39
39
  )
40
40
  bundle_root: Optional[str] = Field(
@@ -69,10 +69,8 @@ class NativeApp(UpdatableModel):
69
69
 
70
70
  @field_validator("artifacts")
71
71
  @classmethod
72
- def transform_artifacts(
73
- cls, orig_artifacts: List[Union[PathMapping, str]]
74
- ) -> List[PathMapping]:
75
- transformed_artifacts = []
72
+ def transform_artifacts(cls, orig_artifacts: Artifacts) -> List[PathMapping]:
73
+ transformed_artifacts: List[PathMapping] = []
76
74
  if orig_artifacts is None:
77
75
  return transformed_artifacts
78
76
 
@@ -104,6 +104,12 @@ class SecurePath:
104
104
  """
105
105
  return self._path.is_file()
106
106
 
107
+ def glob(self, pattern: str):
108
+ """
109
+ Return a generator yielding Path objects that match the given pattern.
110
+ """
111
+ return self._path.glob(pattern)
112
+
107
113
  @property
108
114
  def name(self) -> str:
109
115
  """A string representing the final path component."""
@@ -92,7 +92,11 @@ class BaseSqlExecutor:
92
92
 
93
93
  def execute_queries(self, queries: str, **kwargs):
94
94
  """Executes multiple SQL queries (passed as one string) and returns the results as a list"""
95
- return list(self._execute_string(dedent(queries), **kwargs))
95
+
96
+ # Without remove_comments=True, connectors might throw an error if there is a comment at the end of the file
97
+ return list(
98
+ self._execute_string(dedent(queries), remove_comments=True, **kwargs)
99
+ )
96
100
 
97
101
 
98
102
  class SqlExecutor(BaseSqlExecutor):
@@ -41,6 +41,10 @@ class StagePath:
41
41
  def stage(self) -> str:
42
42
  return self._stage_name
43
43
 
44
+ @property
45
+ def stage_fqn(self) -> FQN:
46
+ return FQN.from_stage(self.stage)
47
+
44
48
  @property
45
49
  def path(self) -> PurePosixPath:
46
50
  return self._path
@@ -156,10 +160,11 @@ class StagePath:
156
160
  def joinpath(self, path: str | Path) -> StagePath:
157
161
  if self.is_file():
158
162
  raise ValueError("Cannot join path to a file")
159
-
163
+ if isinstance(path, Path):
164
+ path = str(PurePosixPath(path))
160
165
  return StagePath(
161
166
  stage_name=self._stage_name,
162
- path=PurePosixPath(self._path) / str(path).lstrip("/"),
167
+ path=PurePosixPath(self._path) / path.lstrip("/"),
163
168
  git_ref=self._git_ref,
164
169
  )
165
170
 
@@ -51,6 +51,9 @@ class Graph(Generic[T]):
51
51
  def get_all_nodes(self) -> set[Node[T]]:
52
52
  return set(self._graph_nodes_map.values())
53
53
 
54
+ def contains_node(self, key: str) -> bool:
55
+ return self.__contains__(key)
56
+
54
57
  def add(self, node: Node[T]) -> None:
55
58
  if node.key in self._graph_nodes_map:
56
59
  raise KeyError(f"Node key {node.key} already exists")
@@ -14,7 +14,11 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import os
17
18
  import sys
19
+ from pathlib import Path
20
+
21
+ from snowflake.cli.api.secure_path import SecurePath
18
22
 
19
23
  BUFFER_SIZE = 4096
20
24
 
@@ -34,3 +38,23 @@ def path_resolver(path_to_file: str) -> str:
34
38
 
35
39
  def is_stage_path(path: str) -> bool:
36
40
  return path.startswith("@") or path.startswith("snow://")
41
+
42
+
43
+ def delete(path: Path) -> None:
44
+ """
45
+ Obliterates whatever is at the given path, or is a no-op if the
46
+ given path does not represent a file or directory that exists.
47
+ """
48
+ spath = SecurePath(path)
49
+ if spath.path.is_file():
50
+ spath.unlink() # remove the file
51
+ elif spath.path.is_dir():
52
+ spath.rmdir(recursive=True) # remove dir and all contains
53
+
54
+
55
+ def resolve_without_follow(path: Path) -> Path:
56
+ """
57
+ Resolves a Path to an absolute version of itself, without following
58
+ symlinks like Path.resolve() does.
59
+ """
60
+ return Path(os.path.abspath(path))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snowflake-cli
3
- Version: 3.3.0
3
+ Version: 3.4.1
4
4
  Summary: Snowflake CLI
5
5
  Project-URL: Source code, https://github.com/snowflakedb/snowflake-cli
6
6
  Project-URL: Bug Tracker, https://github.com/snowflakedb/snowflake-cli/issues
@@ -217,32 +217,31 @@ Classifier: Programming Language :: Python :: 3 :: Only
217
217
  Classifier: Programming Language :: SQL
218
218
  Classifier: Topic :: Database
219
219
  Requires-Python: >=3.10
220
- Requires-Dist: click==8.1.7
221
- Requires-Dist: gitpython==3.1.43
220
+ Requires-Dist: gitpython==3.1.44
222
221
  Requires-Dist: jinja2==3.1.5
223
222
  Requires-Dist: packaging
224
223
  Requires-Dist: pip
225
224
  Requires-Dist: pluggy==1.5.0
226
- Requires-Dist: pydantic==2.9.2
225
+ Requires-Dist: pydantic==2.10.4
227
226
  Requires-Dist: pyyaml==6.0.2
228
227
  Requires-Dist: requests==2.32.3
229
228
  Requires-Dist: requirements-parser==0.11.0
230
229
  Requires-Dist: rich==13.9.4
231
- Requires-Dist: setuptools==75.6.0
232
- Requires-Dist: snowflake-connector-python[secure-local-storage]==3.12.4
230
+ Requires-Dist: setuptools==75.8.0
231
+ Requires-Dist: snowflake-connector-python[secure-local-storage]==3.13.2
233
232
  Requires-Dist: snowflake-core==1.0.2; python_version < '3.12'
234
233
  Requires-Dist: snowflake-snowpark-python<1.26.0,>=1.15.0; python_version < '3.12'
235
234
  Requires-Dist: tomlkit==0.13.2
236
235
  Requires-Dist: typer==0.12.5
237
- Requires-Dist: urllib3<2.3,>=1.24.3
236
+ Requires-Dist: urllib3<2.4,>=1.24.3
238
237
  Provides-Extra: development
239
238
  Requires-Dist: coverage==7.6.10; extra == 'development'
240
239
  Requires-Dist: factory-boy==3.3.1; extra == 'development'
241
- Requires-Dist: faker==33.1.0; extra == 'development'
240
+ Requires-Dist: faker==35.2.0; extra == 'development'
242
241
  Requires-Dist: pre-commit>=3.5.0; extra == 'development'
243
242
  Requires-Dist: pytest-randomly==3.16.0; extra == 'development'
244
243
  Requires-Dist: pytest==8.3.4; extra == 'development'
245
- Requires-Dist: syrupy==4.8.0; extra == 'development'
244
+ Requires-Dist: syrupy==4.8.1; extra == 'development'
246
245
  Provides-Extra: packaging
247
246
  Requires-Dist: pyinstaller~=6.10; extra == 'packaging'
248
247
  Description-Content-Type: text/markdown