Skip to content

Commit b7ad653

Browse files
remove catalog types and snowflake.core dependency (#3950)
1 parent 4a13bfe commit b7ad653

File tree

4 files changed

+221
-18
lines changed

4 files changed

+221
-18
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
- Catalog API now uses SQL commands instead of SnowAPI calls. This new implementation is more reliable now.
1414

15+
#### Dependency Updates
16+
17+
- Catalog API no longer uses types declared in `snowflake.core` and therefore this dependency was removed.
18+
1519
### Snowpark pandas API Updates
1620

1721
#### New Features

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
"pytest-assume", # sql counter check
6464
"decorator", # sql counter check
6565
"tox", # used for setting up testing environments
66-
"snowflake.core>=1.0.0, <2", # Catalog
6766
"psutil", # testing for telemetry
6867
"lxml", # used in XML reader unit tests
6968
]
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#
2+
# Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved.
3+
#
4+
5+
import keyword
6+
import json
7+
8+
from collections.abc import Mapping
9+
10+
from typing import (
11+
Any,
12+
Dict,
13+
Iterable,
14+
Iterator,
15+
Tuple,
16+
Optional,
17+
Union,
18+
)
19+
20+
21+
class ImmutableAttrDict(Mapping):
22+
"""
23+
An immutable mapping whose (identifier-valid, non-keyword, non-private) keys
24+
are also available as read-only attributes. Nested mappings are recursively
25+
wrapped as ImmutableAttrDict instances.
26+
27+
By default also recursively freezes:
28+
- dict / Mapping -> ImmutableAttrDict
29+
- list / tuple -> tuple
30+
- set -> frozenset
31+
- other types -> unchanged
32+
33+
Access:
34+
d.foo (if 'foo' is a valid identifier key)
35+
d['foo'] (always works)
36+
37+
Invalid / reserved keys (not identifiers, keywords, or starting with '_')
38+
are still accessible via item lookup but not as attributes.
39+
"""
40+
41+
__slots__ = ("_data", "_frozen")
42+
43+
# ------------- Construction / conversion helpers -----------------
44+
@classmethod
45+
def _convert(cls, value: Any) -> Any:
46+
"""Recursively convert nested structures."""
47+
if isinstance(value, cls):
48+
return value
49+
if isinstance(value, Mapping):
50+
# Ensure plain dict form for predictability
51+
return cls(value)
52+
if isinstance(value, list) or isinstance(value, tuple):
53+
return tuple(cls._convert(v) for v in value)
54+
if isinstance(value, set):
55+
return frozenset(cls._convert(v) for v in value)
56+
# For other iterables you could add handling if desired.
57+
return value
58+
59+
@staticmethod
60+
def _is_exposable_attr_name(name: str) -> bool:
61+
"""Return True if name can safely be set as an attribute."""
62+
return bool(
63+
name
64+
and name[0] != "_"
65+
and name.isidentifier()
66+
and not keyword.iskeyword(name)
67+
)
68+
69+
def __init__(
70+
self,
71+
mapping: Optional[Union[Mapping[str, Any], Iterable[Tuple[str, Any]]]] = None,
72+
**kwargs: Any,
73+
) -> None:
74+
combined: Dict[str, Any] = {}
75+
if mapping is not None:
76+
if isinstance(mapping, Mapping):
77+
combined.update(mapping)
78+
else:
79+
for k, v in mapping:
80+
combined[k] = v
81+
combined.update(kwargs)
82+
83+
# Deep-convert values first
84+
converted: Dict[str, Any] = {k: self._convert(v) for k, v in combined.items()}
85+
86+
object.__setattr__(self, "_data", converted)
87+
88+
# Expose attribute names only for "safe" keys
89+
for k, v in converted.items():
90+
if self._is_exposable_attr_name(k):
91+
object.__setattr__(self, k, v)
92+
93+
object.__setattr__(self, "_frozen", True)
94+
95+
# ------------- Factory methods -----------------
96+
@classmethod
97+
def from_json(cls, j: str) -> "ImmutableAttrDict":
98+
parsed = json.loads(j)
99+
if not isinstance(parsed, Mapping):
100+
raise TypeError("JSON root must be an object to build ImmutableAttrDict")
101+
return cls(parsed)
102+
103+
# ------------- Mapping interface -----------------
104+
def __getitem__(self, key: str) -> Any:
105+
return self._data[key]
106+
107+
def __iter__(self) -> Iterator[str]:
108+
return iter(self._data)
109+
110+
def __len__(self) -> int:
111+
return len(self._data)
112+
113+
def __contains__(self, key: object) -> bool:
114+
return key in self._data
115+
116+
def get(self, key: str, default: Any = None) -> Any:
117+
return self._data.get(key, default)
118+
119+
# ------------- Attribute fallback -----------------
120+
def __getattr__(self, name: str) -> Any:
121+
data = object.__getattribute__(self, "_data")
122+
if name in data and self._is_exposable_attr_name(name):
123+
return data[name]
124+
raise AttributeError(
125+
f"{type(self).__name__!r} object has no attribute {name!r}"
126+
)
127+
128+
# ------------- Immutability enforcement -----------------
129+
def __setattr__(self, name: str, value: Any) -> None:
130+
if name.startswith("_"):
131+
object.__setattr__(self, name, value)
132+
return
133+
if object.__getattribute__(self, "_frozen"):
134+
raise AttributeError(
135+
f"{type(self).__name__} is immutable; cannot set attribute {name!r}"
136+
)
137+
object.__setattr__(self, name, value)
138+
139+
def __delattr__(self, name: str) -> None:
140+
if name.startswith("_"):
141+
object.__delattr__(self, name)
142+
return
143+
if object.__getattribute__(self, "_frozen"):
144+
raise AttributeError(
145+
f"{type(self).__name__} is immutable; cannot delete attribute {name!r}"
146+
)
147+
object.__delattr__(self, name)
148+
149+
# ------------- Utilities -----------------
150+
def to_dict(self) -> Dict[str, Any]:
151+
"""
152+
Return a shallow copy of the underlying storage.
153+
Nested ImmutableAttrDict instances are preserved (not converted).
154+
For a fully "plain" structure you can use to_plain().
155+
"""
156+
return dict(self._data)
157+
158+
def to_plain(self) -> Dict[str, Any]:
159+
"""
160+
Recursively convert back to plain Python types:
161+
ImmutableAttrDict -> dict
162+
tuple/frozenset -> list (or list for frozenset)
163+
"""
164+
165+
def unwrap(v: Any) -> Any:
166+
if isinstance(v, ImmutableAttrDict):
167+
return {k: unwrap(v._data[k]) for k in v._data}
168+
if isinstance(v, tuple):
169+
return [unwrap(x) for x in v]
170+
if isinstance(v, frozenset):
171+
return [unwrap(x) for x in v]
172+
return v
173+
174+
return {k: unwrap(v) for k, v in self._data.items()}
175+
176+
def __repr__(self) -> str:
177+
return f"{type(self).__name__}({self._data})"
178+
179+
def __reduce__(self):
180+
return (type(self), (self._data,))

src/snowflake/snowpark/catalog.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,17 @@
44

55
from ctypes import ArgumentError
66
import re
7-
from typing import List, Optional, Union, TYPE_CHECKING
7+
from typing import (
8+
List,
9+
Optional,
10+
Union,
11+
TYPE_CHECKING,
12+
)
813

914
from snowflake.snowpark._internal.analyzer.analyzer_utils import unquote_if_quoted
15+
from snowflake.snowpark._immutable_attr_dict import ImmutableAttrDict
1016
from snowflake.snowpark.exceptions import SnowparkSQLException, NotFoundError
1117

12-
try:
13-
from snowflake.core.database import Database # type: ignore
14-
from snowflake.core.database._generated.models import Database as ModelDatabase # type: ignore
15-
from snowflake.core.procedure import Procedure
16-
from snowflake.core.schema import Schema # type: ignore
17-
from snowflake.core.schema._generated.models import Schema as ModelSchema # type: ignore
18-
from snowflake.core.table import Table, TableColumn
19-
from snowflake.core.user_defined_function import UserDefinedFunction
20-
from snowflake.core.view import View
21-
except ImportError as e:
22-
raise ImportError(
23-
"Missing optional dependency: 'snowflake.core'."
24-
) from e # pragma: no cover
25-
2618
from snowflake.snowpark._internal.type_utils import (
2719
convert_sp_to_sf_type,
2820
type_string_to_type_object,
@@ -34,6 +26,34 @@
3426
from snowflake.snowpark.session import Session
3527

3628

29+
class Database(ImmutableAttrDict):
30+
...
31+
32+
33+
class Schema(ImmutableAttrDict):
34+
...
35+
36+
37+
class View(ImmutableAttrDict):
38+
...
39+
40+
41+
class Procedure(ImmutableAttrDict):
42+
...
43+
44+
45+
class UserDefinedFunction(ImmutableAttrDict):
46+
...
47+
48+
49+
class Table(ImmutableAttrDict):
50+
...
51+
52+
53+
class TableColumn(ImmutableAttrDict):
54+
...
55+
56+
3757
class Catalog:
3858
"""The Catalog class provides methods to interact with and manage the Snowflake objects.
3959
It allows users to list, get, and drop various database objects such as databases, schemas, tables,
@@ -191,7 +211,7 @@ def list_databases(
191211

192212
return list(
193213
map(
194-
lambda row: Database._from_model(ModelDatabase.from_json(str(row[0]))),
214+
lambda row: Database.from_json(str(row[0])),
195215
df.collect(),
196216
)
197217
)
@@ -230,7 +250,7 @@ def list_schemas(
230250

231251
return list(
232252
map(
233-
lambda row: Schema._from_model(ModelSchema.from_json(str(row[0]))),
253+
lambda row: Schema.from_json(str(row[0])),
234254
df.collect(),
235255
)
236256
)

0 commit comments

Comments
 (0)