Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(api): ddl accessor #9832

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 198 additions & 44 deletions ibis/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,43 @@ class TablesAccessor(collections.abc.Mapping):
def __init__(self, backend: BaseBackend) -> None:
self._backend = backend

def _execute_if_exists(
self, method_name: str, database=None, like=None
) -> list[str]:
"""Executes method if it exists and it doesn't raise a NotImplementedError, else returns an empty list."""
try:
method = getattr(self._backend.ddl, method_name)
return method(database=database, like=like)
except NotImplementedError:
pass
return []

def _gather_tables(self, database=None, like=None) -> list[str]:
"""Gathers table names using the list_* methods available on the backend."""
# TODO: break this down into views/tables to be more explicit in repr (see #9859)
# list_* methods that might exist on a given backends.
list_methods = [
"list_tables",
"list_temp_tables",
"list_views",
"list_temp_views",
"list_foreign_tables",
"list_materialized_views",
]
tables = []
for method_name in list_methods:
tables.extend(
self._execute_if_exists(method_name, database=database, like=like)
)
return tables

def __call__(self, database=None, like=None):
return self._gather_tables(database, like)

@property
def _tables(self) -> list[str]:
return self._gather_tables()

def __getitem__(self, name) -> ir.Table:
try:
return self._backend.table(name)
Expand All @@ -63,29 +100,179 @@ def __getattr__(self, name) -> ir.Table:
raise AttributeError(name) from exc

def __iter__(self) -> Iterator[str]:
return iter(sorted(self._backend.list_tables()))
return iter(sorted(self._tables))

def __len__(self) -> int:
return len(self._backend.list_tables())
return len(self._tables)

def __dir__(self) -> list[str]:
o = set()
o.update(dir(type(self)))
o.update(
name
for name in self._backend.list_tables()
for name in self._tables
if name.isidentifier() and not keyword.iskeyword(name)
)
return list(o)

def __repr__(self) -> str:
tables = self._backend.list_tables()
rows = ["Tables", "------"]
rows.extend(f"- {name}" for name in sorted(tables))
rows.extend(f"- {name}" for name in sorted(self._tables))
return "\n".join(rows)

def _ipython_key_completions_(self) -> list[str]:
return self._backend.list_tables()
return self._tables


class DDLAccessor:
"""ddl accessor list views."""

def __init__(self, backend: BaseBackend) -> None:
self._backend = backend
ncclementi marked this conversation as resolved.
Show resolved Hide resolved

def _raise_if_not_implemented(self, method_name: str):
try:
getattr(self._backend, method_name)
except AttributeError as e:
if f"has no attribute '{method_name}'" in str(e):
raise NotImplementedError(
f"The method {method_name} is not implemented for the {self._backend.name} backend"
)

def list_tables(
self, like: str | None = None, database: tuple[str, str] | str | None = None
) -> list[str]:
"""Return the list of table names in a database via the backend's implementation.

::: {.callout-note}
## Ibis does not use the word `schema` to refer to database hierarchy.

A collection of tables is referred to as a `database`.
A collection of `database` is referred to as a `catalog`.

These terms are mapped onto the corresponding features in each
backend (where available), regardless of whether the backend itself
uses the same terminology.
:::

Parameters
----------
like
A pattern to use for listing tables.
database
Database to list tables from. Default behavior is to show tables in
the current database.
"""

self._raise_if_not_implemented("_list_tables")
return self._backend._list_tables(like=like, database=database)

def list_temp_tables(
self, like: str | None = None, database: tuple[str, str] | str | None = None
) -> list[str]:
"""Return the list of temporary table names in a database via the backend's implementation.

::: {.callout-note}
## Ibis does not use the word `schema` to refer to database hierarchy.

A collection of tables is referred to as a `database`.
A collection of `database` is referred to as a `catalog`.

These terms are mapped onto the corresponding features in each
backend (where available), regardless of whether the backend itself
uses the same terminology.
:::

Parameters
----------
like
A pattern to use for listing tables.
database
Database to list tables from. Default behavior is to show tables in
the current database.
"""

self._raise_if_not_implemented("_list_temp_tables")
return self._backend._list_temp_tables(like=like, database=database)

def list_views(
self, like: str | None = None, database: tuple[str, str] | str | None = None
) -> list[str]:
"""Return the list of view names in a database via the backend's implementation.

::: {.callout-note}
## Ibis does not use the word `schema` to refer to database hierarchy.

A collection of tables is referred to as a `database`.
A collection of `database` is referred to as a `catalog`.

These terms are mapped onto the corresponding features in each
backend (where available), regardless of whether the backend itself
uses the same terminology.
:::

Parameters
----------
like
A pattern to use for listing tables.
database
Database to list tables from. Default behavior is to show tables in
the current database.
"""

self._raise_if_not_implemented("_list_views")
return self._backend._list_views(like=like, database=database)

def list_temp_views(
self, like: str | None = None, database: tuple[str, str] | str | None = None
) -> list[str]:
"""Return the list of temporary view names in a database via the backend's implementation.

::: {.callout-note}
## Ibis does not use the word `schema` to refer to database hierarchy.

A collection of tables is referred to as a `database`.
A collection of `database` is referred to as a `catalog`.

These terms are mapped onto the corresponding features in each
backend (where available), regardless of whether the backend itself
uses the same terminology.
:::

Parameters
----------
like
A pattern to use for listing tables.
database
Database to list tables from. Default behavior is to show tables in
the current database.
"""

self._raise_if_not_implemented("_list_temp_views")
return self._backend._list_temp_views(like=like, database=database)

def list_materialized_views(
self, like: str | None = None, database: tuple[str, str] | str | None = None
) -> list[str]:
self._raise_if_not_implemented("_list_materialized_views")
return self._backend._list_materialized_views(like=like, database=database)

def list_foreign_tables(
self, like: str | None = None, database: tuple[str, str] | str | None = None
) -> list[str]:
"""Return the list of foreign table names via the backend's implementation.

Parameters
----------
like
A pattern to use for listing tables.
database
Database to list foreign tables from. Default behavior is to show tables in
the current database.
"""

self._raise_if_not_implemented("_list_foreign_tables")
return self._backend._list_foreign_tables(like=like, database=database)


class _FileIOHandler:
Expand Down Expand Up @@ -947,44 +1134,6 @@ def _filter_with_like(values: Iterable[str], like: str | None = None) -> list[st
pattern = re.compile(like)
return sorted(filter(pattern.findall, values))

@abc.abstractmethod
def list_tables(
self, like: str | None = None, database: tuple[str, str] | str | None = None
) -> list[str]:
"""Return the list of table names in the current database.

For some backends, the tables may be files in a directory,
or other equivalent entities in a SQL database.

::: {.callout-note}
## Ibis does not use the word `schema` to refer to database hierarchy.

A collection of tables is referred to as a `database`.
A collection of `database` is referred to as a `catalog`.

These terms are mapped onto the corresponding features in each
backend (where available), regardless of whether the backend itself
uses the same terminology.
:::

Parameters
----------
like
A pattern in Python's regex format.
database
The database from which to list tables.
If not provided, the current database is used.
For backends that support multi-level table hierarchies, you can
pass in a dotted string path like `"catalog.database"` or a tuple of
strings like `("catalog", "database")`.

Returns
-------
list[str]
The list of the table names that match the pattern `like`.

"""

@abc.abstractmethod
def table(
self, name: str, database: tuple[str, str] | str | None = None
Expand Down Expand Up @@ -1035,6 +1184,11 @@ def tables(self):
"""
return TablesAccessor(self)

@property
def ddl(self):
"""A ddl accessor."""
return DDLAccessor(self)

@property
@abc.abstractmethod
def version(self) -> str:
Expand Down
Loading
Loading