7
7
import keyword
8
8
import re
9
9
import urllib .parse
10
- import weakref
11
10
from pathlib import Path
12
11
from typing import TYPE_CHECKING , Any , ClassVar
13
12
35
34
class TablesAccessor (collections .abc .Mapping ):
36
35
"""A mapping-like object for accessing tables off a backend.
37
36
38
- ::: {.callout-note}
39
- ## The `tables` accessor is tied to the lifetime of the backend.
40
-
41
- If the backend goes out of scope, the `tables` accessor is no longer valid.
42
- :::
43
-
44
37
Tables may be accessed by name using either index or attribute access:
45
38
46
39
Examples
@@ -53,6 +46,42 @@ class TablesAccessor(collections.abc.Mapping):
53
46
def __init__ (self , backend : BaseBackend ) -> None :
54
47
self ._backend = backend
55
48
49
+ def _execute_if_exists (
50
+ self , method_name : str , database = None , like = None
51
+ ) -> list [str ]:
52
+ """Executes method if it exists and it doesn't raise a NotImplementedError, else returns an empty list."""
53
+ method = getattr (self ._backend .ddl , method_name )
54
+ if callable (method ):
55
+ try :
56
+ return method (database = database , like = like )
57
+ except NotImplementedError :
58
+ pass
59
+ return []
60
+
61
+ def _gather_tables (self , database = None , like = None ) -> list [str ]:
62
+ """Gathers table names using the list_* methods available on the backend."""
63
+ # TODO: break this down into views/tables to be more explicit in repr (see #9859)
64
+ # list_* methods that might exist on a given backends.
65
+ list_methods = [
66
+ "list_tables" ,
67
+ "list_temp_tables" ,
68
+ "list_views" ,
69
+ "list_temp_views" ,
70
+ ]
71
+ tables = []
72
+ for method_name in list_methods :
73
+ tables .extend (
74
+ self ._execute_if_exists (method_name , database = database , like = like )
75
+ )
76
+ return list (set (tables ))
77
+
78
+ def __call__ (self , database = None , like = None ):
79
+ return self ._gather_tables (database , like )
80
+
81
+ @property
82
+ def _tables (self ) -> list [str ]:
83
+ return self ._gather_tables ()
84
+
56
85
def __getitem__ (self , name ) -> ir .Table :
57
86
try :
58
87
return self ._backend .table (name )
@@ -68,29 +97,70 @@ def __getattr__(self, name) -> ir.Table:
68
97
raise AttributeError (name ) from exc
69
98
70
99
def __iter__ (self ) -> Iterator [str ]:
71
- return iter (sorted (self ._backend . list_tables () ))
100
+ return iter (sorted (self ._tables ))
72
101
73
102
def __len__ (self ) -> int :
74
- return len (self ._backend . list_tables () )
103
+ return len (self ._tables )
75
104
76
105
def __dir__ (self ) -> list [str ]:
77
106
o = set ()
78
107
o .update (dir (type (self )))
79
108
o .update (
80
109
name
81
- for name in self ._backend . list_tables ()
110
+ for name in self ._tables
82
111
if name .isidentifier () and not keyword .iskeyword (name )
83
112
)
84
113
return list (o )
85
114
86
115
def __repr__ (self ) -> str :
87
- tables = self ._backend .list_tables ()
88
116
rows = ["Tables" , "------" ]
89
- rows .extend (f"- { name } " for name in sorted (tables ))
117
+ rows .extend (f"- { name } " for name in sorted (self . _tables ))
90
118
return "\n " .join (rows )
91
119
92
120
def _ipython_key_completions_ (self ) -> list [str ]:
93
- return self ._backend .list_tables ()
121
+ return self ._tables
122
+
123
+
124
+ class DDLAccessor :
125
+ """ddl accessor list views."""
126
+
127
+ def __init__ (self , backend : BaseBackend ) -> None :
128
+ self ._backend = backend
129
+
130
+ def _raise_if_not_implemented (self , method_name : str ):
131
+ method = getattr (self ._backend , method_name )
132
+ if not callable (method ):
133
+ raise NotImplementedError (
134
+ f"The method { method_name } is not implemented for the { self ._backend .name } backend"
135
+ )
136
+
137
+ def list_tables (
138
+ self , like : str | None = None , database : tuple [str , str ] | str | None = None
139
+ ) -> list [str ]:
140
+ """Return the list of table names via the backend's implementation."""
141
+ self ._raise_if_not_implemented ("_list_tables" )
142
+ return self ._backend ._list_tables (like = like , database = database )
143
+
144
+ def list_temp_tables (
145
+ self , like : str | None = None , database : tuple [str , str ] | str | None = None
146
+ ) -> list [str ]:
147
+ """Return the list of temporary table names via the backend's implementation."""
148
+ self ._raise_if_not_implemented ("_list_temp_tables" )
149
+ return self ._backend ._list_temp_tables (like = like , database = database )
150
+
151
+ def list_views (
152
+ self , like : str | None = None , database : tuple [str , str ] | str | None = None
153
+ ) -> list [str ]:
154
+ """Return the list of view names via the backend's implementation."""
155
+ self ._raise_if_not_implemented ("_list_views" )
156
+ return self ._backend ._list_views (like = like , database = database )
157
+
158
+ def list_temp_views (
159
+ self , like : str | None = None , database : tuple [str , str ] | str | None = None
160
+ ) -> list [str ]:
161
+ """Return the list of temp view names via the backend's implementation."""
162
+ self ._raise_if_not_implemented ("_list_temp_views" )
163
+ return self ._backend ._list_temp_views (like = like , database = database )
94
164
95
165
96
166
class _FileIOHandler :
@@ -811,7 +881,12 @@ def __init__(self, *args, **kwargs):
811
881
self ._con_args : tuple [Any ] = args
812
882
self ._con_kwargs : dict [str , Any ] = kwargs
813
883
self ._can_reconnect : bool = True
814
- self ._query_cache = RefCountedCache (weakref .proxy (self ))
884
+ # expression cache
885
+ self ._query_cache = RefCountedCache (
886
+ populate = self ._load_into_cache ,
887
+ lookup = lambda name : self .table (name ).op (),
888
+ finalize = self ._clean_up_cached_table ,
889
+ )
815
890
816
891
@property
817
892
@abc .abstractmethod
@@ -933,44 +1008,6 @@ def _filter_with_like(values: Iterable[str], like: str | None = None) -> list[st
933
1008
pattern = re .compile (like )
934
1009
return sorted (filter (pattern .findall , values ))
935
1010
936
- @abc .abstractmethod
937
- def list_tables (
938
- self , like : str | None = None , database : tuple [str , str ] | str | None = None
939
- ) -> list [str ]:
940
- """Return the list of table names in the current database.
941
-
942
- For some backends, the tables may be files in a directory,
943
- or other equivalent entities in a SQL database.
944
-
945
- ::: {.callout-note}
946
- ## Ibis does not use the word `schema` to refer to database hierarchy.
947
-
948
- A collection of tables is referred to as a `database`.
949
- A collection of `database` is referred to as a `catalog`.
950
-
951
- These terms are mapped onto the corresponding features in each
952
- backend (where available), regardless of whether the backend itself
953
- uses the same terminology.
954
- :::
955
-
956
- Parameters
957
- ----------
958
- like
959
- A pattern in Python's regex format.
960
- database
961
- The database from which to list tables.
962
- If not provided, the current database is used.
963
- For backends that support multi-level table hierarchies, you can
964
- pass in a dotted string path like `"catalog.database"` or a tuple of
965
- strings like `("catalog", "database")`.
966
-
967
- Returns
968
- -------
969
- list[str]
970
- The list of the table names that match the pattern `like`.
971
-
972
- """
973
-
974
1011
@abc .abstractmethod
975
1012
def table (
976
1013
self , name : str , database : tuple [str , str ] | str | None = None
@@ -1019,7 +1056,12 @@ def tables(self):
1019
1056
>>> people = con.tables.people # access via attribute
1020
1057
1021
1058
"""
1022
- return TablesAccessor (weakref .proxy (self ))
1059
+ return TablesAccessor (self )
1060
+
1061
+ @property
1062
+ def ddl (self ):
1063
+ """A ddl accessor."""
1064
+ return DDLAccessor (self )
1023
1065
1024
1066
@property
1025
1067
@abc .abstractmethod
0 commit comments