Skip to content

Commit 320eb6d

Browse files
authored
Merge pull request #322 from Meghansaha/add-print-available-tables
Add `print_database_tables()`
2 parents 8c6912f + cdb72a9 commit 320eb6d

File tree

4 files changed

+400
-81
lines changed

4 files changed

+400
-81
lines changed

docs/_quarto.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ quartodoc:
248248
- name: load_dataset
249249
- name: get_data_path
250250
- name: connect_to_table
251+
- name: print_database_tables
251252
- title: YAML
252253
desc: >
253254
The *YAML* group contains functions that allow for the use of YAML to orchestrate validation

pointblank/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
load_dataset,
4040
missing_vals_tbl,
4141
preview,
42+
print_database_tables,
4243
read_file,
4344
write_file,
4445
)
@@ -73,6 +74,7 @@
7374
"get_data_path",
7475
"config",
7576
"connect_to_table",
77+
"print_database_tables",
7678
"preview",
7779
"missing_vals_tbl",
7880
"get_action_metadata",

pointblank/validate.py

Lines changed: 124 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
"write_file",
124124
"config",
125125
"connect_to_table",
126+
"print_database_tables",
126127
"preview",
127128
"missing_vals_tbl",
128129
"get_action_metadata",
@@ -3919,6 +3920,47 @@ def _has_notes(self) -> bool:
39193920
return self.notes is not None and len(self.notes) > 0
39203921

39213922

3923+
def _handle_connection_errors(e: Exception, connection_string: str) -> None:
3924+
"""
3925+
Shared error handling for database connection failures.
3926+
3927+
Raises appropriate ConnectionError with helpful messages based on the exception.
3928+
"""
3929+
3930+
error_str = str(e).lower()
3931+
backend_install_map = {
3932+
"duckdb": "pip install 'ibis-framework[duckdb]'",
3933+
"postgresql": "pip install 'ibis-framework[postgres]'",
3934+
"postgres": "pip install 'ibis-framework[postgres]'",
3935+
"mysql": "pip install 'ibis-framework[mysql]'",
3936+
"sqlite": "pip install 'ibis-framework[sqlite]'",
3937+
"bigquery": "pip install 'ibis-framework[bigquery]'",
3938+
"snowflake": "pip install 'ibis-framework[snowflake]'",
3939+
}
3940+
3941+
# Check if this is a missing backend dependency
3942+
for backend, install_cmd in backend_install_map.items():
3943+
if backend in error_str and ("not found" in error_str or "no module" in error_str):
3944+
raise ConnectionError(
3945+
f"Missing {backend.upper()} backend for Ibis. Install it with:\n"
3946+
f" {install_cmd}\n\n"
3947+
f"Original error: {e}"
3948+
) from e
3949+
3950+
# Generic connection error
3951+
raise ConnectionError( # pragma: no cover
3952+
f"Failed to connect using: {connection_string}\n"
3953+
f"Error: {e}\n\n"
3954+
f"Supported connection string formats:\n"
3955+
f"- DuckDB: 'duckdb:///path/to/file.ddb'\n"
3956+
f"- SQLite: 'sqlite:///path/to/file.db'\n"
3957+
f"- PostgreSQL: 'postgresql://user:pass@host:port/db'\n"
3958+
f"- MySQL: 'mysql://user:pass@host:port/db'\n"
3959+
f"- BigQuery: 'bigquery://project/dataset'\n"
3960+
f"- Snowflake: 'snowflake://user:pass@account/db/schema'"
3961+
) from e
3962+
3963+
39223964
def connect_to_table(connection_string: str) -> Any:
39233965
"""
39243966
Connect to a database table using a connection string.
@@ -3998,7 +4040,11 @@ def connect_to_table(connection_string: str) -> Any:
39984040
pip install 'ibis-framework[duckdb]' # for DuckDB
39994041
pip install 'ibis-framework[postgres]' # for PostgreSQL
40004042
```
4043+
See Also
4044+
--------
4045+
print_database_tables : List all available tables in a database for discovery
40014046
"""
4047+
40024048
# Check if Ibis is available
40034049
if not _is_lib_present(lib_name="ibis"):
40044050
raise ImportError(
@@ -4012,14 +4058,10 @@ def connect_to_table(connection_string: str) -> Any:
40124058
if "::" not in connection_string:
40134059
# Try to connect to get available tables for helpful error message
40144060
try:
4015-
# Extract the base connection string (without table name)
40164061
base_connection = connection_string
4017-
4018-
# Connect to the database
40194062
conn = ibis.connect(base_connection)
40204063

4021-
# Get list of available tables
4022-
try:
4064+
try: # pragma: no cover
40234065
available_tables = conn.list_tables()
40244066
except Exception: # pragma: no cover
40254067
available_tables = []
@@ -4036,7 +4078,6 @@ def connect_to_table(connection_string: str) -> Any:
40364078
f" {connection_string}::TABLE_NAME\n\n"
40374079
f"Examples:\n"
40384080
)
4039-
# Add examples with first few table names
40404081
for table in available_tables[:3]:
40414082
error_msg += f" {connection_string}::{table}\n"
40424083
else:
@@ -4051,43 +4092,8 @@ def connect_to_table(connection_string: str) -> Any:
40514092

40524093
except Exception as e:
40534094
if isinstance(e, ValueError):
4054-
raise # Re-raise our custom ValueError
4055-
4056-
# Check for backend-specific errors and provide installation guidance
4057-
error_str = str(e).lower()
4058-
backend_install_map = {
4059-
"duckdb": "pip install 'ibis-framework[duckdb]'",
4060-
"postgresql": "pip install 'ibis-framework[postgres]'",
4061-
"postgres": "pip install 'ibis-framework[postgres]'",
4062-
"mysql": "pip install 'ibis-framework[mysql]'",
4063-
"sqlite": "pip install 'ibis-framework[sqlite]'",
4064-
"bigquery": "pip install 'ibis-framework[bigquery]'",
4065-
"snowflake": "pip install 'ibis-framework[snowflake]'",
4066-
}
4067-
4068-
# Check if this is a missing backend dependency
4069-
for backend, install_cmd in backend_install_map.items(): # pragma: no cover
4070-
if backend in error_str and ("not found" in error_str or "no module" in error_str):
4071-
raise ConnectionError(
4072-
f"Missing {backend.upper()} backend for Ibis. Install it with:\n"
4073-
f" {install_cmd}\n\n"
4074-
f"Original error: {e}\n\n"
4075-
f"Supported connection string formats:\n"
4076-
f"- DuckDB: 'duckdb:///path/to/file.ddb::table_name'\n"
4077-
f"- SQLite: 'sqlite:///path/to/file.db::table_name'\n"
4078-
f"- PostgreSQL: 'postgresql://user:pass@host:port/db::table_name'\n"
4079-
f"- MySQL: 'mysql://user:pass@host:port/db::table_name'\n"
4080-
f"- BigQuery: 'bigquery://project/dataset::table_name'\n"
4081-
f"- Snowflake: 'snowflake://user:pass@account/db/schema::table_name'\n"
4082-
f"\nNote: Use '::table_name' to specify the table within the database."
4083-
) from e
4084-
4085-
# Generic connection error
4086-
raise ConnectionError( # pragma: no cover
4087-
f"Failed to connect to database using connection string: {connection_string}\n"
4088-
f"Error: {e}\n\n"
4089-
f"No table specified. Use the format: {connection_string}::TABLE_NAME"
4090-
) from e
4095+
raise
4096+
_handle_connection_errors(e, connection_string)
40914097

40924098
# Split connection string and table name
40934099
try:
@@ -4100,56 +4106,94 @@ def connect_to_table(connection_string: str) -> Any:
41004106
conn = ibis.connect(base_connection)
41014107
table = conn.table(table_name)
41024108
return table
4103-
41044109
except Exception as e:
4105-
# Check for backend-specific errors and provide installation guidance
41064110
error_str = str(e).lower()
4107-
backend_install_map = {
4108-
"duckdb": "pip install 'ibis-framework[duckdb]'",
4109-
"postgresql": "pip install 'ibis-framework[postgres]'",
4110-
"postgres": "pip install 'ibis-framework[postgres]'",
4111-
"mysql": "pip install 'ibis-framework[mysql]'",
4112-
"sqlite": "pip install 'ibis-framework[sqlite]'",
4113-
"bigquery": "pip install 'ibis-framework[bigquery]'",
4114-
"snowflake": "pip install 'ibis-framework[snowflake]'",
4115-
}
4116-
4117-
# Check if this is a missing backend dependency
4118-
for backend, install_cmd in backend_install_map.items():
4119-
if backend in error_str and ("not found" in error_str or "no module" in error_str):
4120-
raise ConnectionError(
4121-
f"Missing {backend.upper()} backend for Ibis. Install it with:\n"
4122-
f" {install_cmd}\n\n"
4123-
f"Original error: {e}"
4124-
) from e
41254111

4126-
# Check if table doesn't exist
4127-
if "table" in error_str and ("not found" in error_str or "does not exist" in error_str):
4128-
# Try to get available tables for helpful message
4112+
# Check if this is a "table not found" error
4113+
if "table" in error_str and (
4114+
"not found" in error_str or "does not exist" in error_str or "not exist" in error_str
4115+
):
4116+
# Try to get available tables for a helpful error message
41294117
try: # pragma: no cover
41304118
available_tables = conn.list_tables()
41314119
if available_tables:
41324120
table_list = "\n".join(f" - {table}" for table in available_tables)
41334121
raise ValueError(
41344122
f"Table '{table_name}' not found in database.\n\n"
41354123
f"Available tables:\n{table_list}\n\n"
4136-
f"Check the table name and try again with:\n"
4137-
f" {base_connection}::CORRECT_TABLE_NAME"
4138-
) from e
4139-
else:
4140-
raise ValueError(
4141-
f"Table '{table_name}' not found and no tables available in database."
4124+
f"Connection: {base_connection}"
41424125
) from e
4126+
except ValueError:
4127+
# Re-raise the table-specific ValueError
4128+
raise
41434129
except Exception:
4144-
raise ValueError(
4145-
f"Table '{table_name}' not found in database. "
4146-
f"Check the table name and connection string."
4147-
) from e
4130+
# If we can't list tables, just raise a simple error
4131+
pass
4132+
4133+
raise ValueError(
4134+
f"Table '{table_name}' not found in database.\n"
4135+
f"Connection: {base_connection}\n\n"
4136+
f"Original error: {e}"
4137+
) from e
4138+
4139+
# For other errors, use the generic connection error handler
4140+
_handle_connection_errors(e, base_connection)
41484141

4149-
# Generic connection error
4150-
raise ConnectionError(
4151-
f"Failed to connect to table '{table_name}' using: {base_connection}\nError: {e}"
4152-
) from e
4142+
4143+
def print_database_tables(connection_string: str) -> list[str]:
4144+
"""
4145+
List all tables in a database from a connection string.
4146+
4147+
The `print_database_tables()` function connects to a database and returns a list of all
4148+
available tables. This is particularly useful for discovering what tables exist in a database
4149+
before connecting to a specific table with `connect_to_table(). The function automatically
4150+
filters out temporary Ibis tables (memtables) to show only user tables. It supports all database
4151+
backends available through Ibis, including DuckDB, SQLite, PostgreSQL, MySQL, BigQuery, and
4152+
Snowflake.
4153+
4154+
Parameters
4155+
----------
4156+
connection_string
4157+
A database connection string WITHOUT the ::table_name suffix. Example:
4158+
`"duckdb:///path/to/database.ddb"`.
4159+
4160+
Returns
4161+
-------
4162+
list[str]
4163+
List of table names, excluding temporary Ibis tables.
4164+
4165+
See Also
4166+
--------
4167+
connect_to_table : Connect to a database table with full connection string documentation
4168+
"""
4169+
# Check if connection string includes table specification (which is not allowed)
4170+
if "::" in connection_string:
4171+
raise ValueError(
4172+
"Connection string should not include table specification (::table_name).\n"
4173+
f"You've supplied: {connection_string}\n"
4174+
f"Expected format: 'duckdb:///path/to/database.ddb' (without ::table_name)"
4175+
)
4176+
4177+
# Check if Ibis is available
4178+
if not _is_lib_present(lib_name="ibis"):
4179+
raise ImportError(
4180+
"The Ibis library is not installed but is required for database connection strings.\n"
4181+
"Install it with: pip install 'ibis-framework[duckdb]' (or other backend as needed)"
4182+
)
4183+
4184+
import ibis
4185+
4186+
try:
4187+
# Connect to database
4188+
conn = ibis.connect(connection_string)
4189+
# Get all tables and filter out temporary Ibis tables
4190+
all_tables = conn.list_tables()
4191+
user_tables = [t for t in all_tables if "memtable" not in t]
4192+
4193+
return user_tables
4194+
4195+
except Exception as e:
4196+
_handle_connection_errors(e, connection_string)
41534197

41544198

41554199
@dataclass

0 commit comments

Comments
 (0)