|
1 | 1 | from __future__ import annotations
|
2 | 2 |
|
3 | 3 | import abc
|
| 4 | +import collections.abc |
4 | 5 | import functools
|
| 6 | +import keyword |
5 | 7 | import re
|
6 | 8 | from pathlib import Path
|
7 |
| -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, Mapping |
| 9 | +from typing import ( |
| 10 | + TYPE_CHECKING, |
| 11 | + Any, |
| 12 | + Callable, |
| 13 | + ClassVar, |
| 14 | + Iterable, |
| 15 | + Iterator, |
| 16 | + Mapping, |
| 17 | +) |
8 | 18 |
|
9 | 19 | if TYPE_CHECKING:
|
10 | 20 | import pandas as pd
|
@@ -145,6 +155,55 @@ def list_tables(self, like=None):
|
145 | 155 | return self.client.list_tables(like, database=self.name)
|
146 | 156 |
|
147 | 157 |
|
| 158 | +class TablesAccessor(collections.abc.Mapping): |
| 159 | + """A mapping-like object for accessing tables off a backend. |
| 160 | +
|
| 161 | + Tables may be accessed by name using either index or attribute access: |
| 162 | +
|
| 163 | + Examples |
| 164 | + -------- |
| 165 | + >>> con = ibis.sqlite.connect("example.db") |
| 166 | + >>> people = con.tables['people'] # access via index |
| 167 | + >>> people = con.tables.people # access via attribute |
| 168 | + """ |
| 169 | + |
| 170 | + def __init__(self, backend: BaseBackend): |
| 171 | + self._backend = backend |
| 172 | + |
| 173 | + def __getitem__(self, name) -> ir.Table: |
| 174 | + try: |
| 175 | + return self._backend.table(name) |
| 176 | + except Exception as exc: |
| 177 | + raise KeyError(name) from exc |
| 178 | + |
| 179 | + def __getattr__(self, name) -> ir.Table: |
| 180 | + if name.startswith("_"): |
| 181 | + raise AttributeError(name) |
| 182 | + try: |
| 183 | + return self._backend.table(name) |
| 184 | + except Exception as exc: |
| 185 | + raise AttributeError(name) from exc |
| 186 | + |
| 187 | + def __iter__(self) -> Iterator[str]: |
| 188 | + return iter(sorted(self._backend.list_tables())) |
| 189 | + |
| 190 | + def __len__(self) -> int: |
| 191 | + return len(self._backend.list_tables()) |
| 192 | + |
| 193 | + def __dir__(self) -> list[str]: |
| 194 | + o = set() |
| 195 | + o.update(dir(type(self))) |
| 196 | + o.update( |
| 197 | + name |
| 198 | + for name in self._backend.list_tables() |
| 199 | + if name.isidentifier() and not keyword.iskeyword(name) |
| 200 | + ) |
| 201 | + return list(o) |
| 202 | + |
| 203 | + def _ipython_key_completions_(self) -> list[str]: |
| 204 | + return self._backend.list_tables() |
| 205 | + |
| 206 | + |
148 | 207 | class BaseBackend(abc.ABC):
|
149 | 208 | """Base backend class.
|
150 | 209 |
|
@@ -368,6 +427,20 @@ def exists_table(self, name: str, database: str | None = None) -> bool:
|
368 | 427 | def table(self, name: str, database: str | None = None) -> ir.Table:
|
369 | 428 | """Return a table expression from the database."""
|
370 | 429 |
|
| 430 | + @functools.cached_property |
| 431 | + def tables(self): |
| 432 | + """An accessor for tables in the database. |
| 433 | +
|
| 434 | + Tables may be accessed by name using either index or attribute access: |
| 435 | +
|
| 436 | + Examples |
| 437 | + -------- |
| 438 | + >>> con = ibis.sqlite.connect("example.db") |
| 439 | + >>> people = con.tables['people'] # access via index |
| 440 | + >>> people = con.tables.people # access via attribute |
| 441 | + """ |
| 442 | + return TablesAccessor(self) |
| 443 | + |
371 | 444 | @deprecated(version='2.0', instead='use `.table(name).schema()`')
|
372 | 445 | def get_schema(self, table_name: str, database: str = None) -> sch.Schema:
|
373 | 446 | """Return the schema of `table_name`."""
|
|
0 commit comments