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

[CIVIS-9192] ENH clean up database credential IDs at civis.APIClient #502

Merged
merged 5 commits into from
Nov 11, 2024
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- `Response` objects are now fully typed through the attribute syntax. (#501)
- Both `Response` and `PaginatedResponse` are now directly available under the `civis` namespace. (#501)
- Added support for Python 3.13. (#501)
- Added the new property `default_database_credential_id` at `civis.APIClient`,
which is going to replace the existing `default_credential`. (#502)

### Changed
- When a `PaginatedResponse` object is returned from an API call,
a user-specified `limit` kwarg is now honored to facilitate speeding up the pagination. (#501)

### Deprecated
- The method `get_database_credential_id` at `civis.APIClient` has been deprecated
and will be removed at civis-python v3.0.0. There's no replacement for this method. (#502)
- The property `default_credential` at `civis.APIClient` has been deprecated
and will be removed at civis-python v3.0.0,
in favor of the new property `default_database_credential_id`. (#502)

### Removed
- Dropped support for Python 3.9. (#499)

Expand Down
2 changes: 1 addition & 1 deletion docs/source/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ the current user.
.. code:: python

>>> db_id = client.get_database_id('cluster-name')
>>> cred_id = client.default_credential
>>> cred_id = client.default_database_credential_id

In order to export a table, we need to write some SQL that will generate the
data to export. Then we create the export job and run it.
Expand Down
30 changes: 29 additions & 1 deletion src/civis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from functools import lru_cache
import logging
import textwrap
import warnings
from typing import TYPE_CHECKING

import civis
Expand Down Expand Up @@ -168,6 +169,18 @@ def get_database_credential_id(self, username, database_name):
>>> client.get_database_credential_id(1111, 'redshift-general')
1111
"""
warnings.warn(
"The method `get_database_credential_id` is deprecated and will be removed "
"at civis-python v3.0.0. Its continued usage is strongly discouraged. "
"Given the way Civis Platform has evolved over the years, "
"there's currently no reliable way to get a database credential ID "
"from a username and database name. No replacement for this method is "
"being planned. If you need to programmatically access a database "
"credential ID that is or may likely be the default credential, "
"consider the property `default_database_credential_id`.",
FutureWarning,
stacklevel=2, # Point to the user code that calls this method.
)
if isinstance(username, int):
return username
else:
Expand Down Expand Up @@ -339,10 +352,25 @@ def get_storage_host_id(self, storage_host):
@lru_cache(maxsize=128)
def default_credential(self):
"""The current user's default credential."""
# NOTE: this should be optional to endpoints...so this could go away
warnings.warn(
"The property `default_credential` is deprecated and will be removed "
"at civis-python v3.0.0. "
"Please use `default_database_credential_id` instead.",
FutureWarning,
stacklevel=2, # Point to the user code that calls this method.
)
creds = self.credentials.list(default=True)
return creds[0]["id"] if len(creds) > 0 else None

@property
@lru_cache(maxsize=128)
def default_database_credential_id(self):
"""The current user's default database credential ID."""
creds = self.credentials.list(
default=True, type="Database", remote_host_id=None
)
return creds[0]["id"] if len(creds) > 0 else None

@property
@lru_cache(maxsize=128)
def username(self):
Expand Down
151 changes: 76 additions & 75 deletions src/civis/client.pyi

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/civis/futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class CivisFuture(PollableResult):
>>> import civis
>>> client = civis.APIClient()
>>> database_id = client.get_database_id("my_database")
>>> cred_id = client.default_credential
>>> cred_id = client.default_database_credential_id
>>> sql = "SELECT 1"
>>> preview_rows = 10
>>> response = client.queries.post(database_id, sql, preview_rows,
Expand Down
6 changes: 3 additions & 3 deletions src/civis/io/_databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def query_civis(
if client is None:
client = APIClient()
database_id = client.get_database_id(database)
cred_id = credential_id or client.default_credential
cred_id = credential_id or client.default_database_credential_id
resp = client.queries.post(
database_id, sql, preview_rows, credential=cred_id, hidden=hidden
)
Expand Down Expand Up @@ -127,8 +127,8 @@ def transfer_table(
"""
if client is None:
client = APIClient()
source_cred_id = source_credential_id or client.default_credential
dest_cred_id = dest_credential_id or client.default_credential
source_cred_id = source_credential_id or client.default_database_credential_id
dest_cred_id = dest_credential_id or client.default_database_credential_id
job_name = maybe_get_random_name(job_name)
source = {
"remote_host_id": client.get_database_id(source_db),
Expand Down
8 changes: 4 additions & 4 deletions src/civis/io/_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def read_civis_sql(
raise ImportError("use_pandas is True but pandas is not installed.")

db_id = client.get_database_id(database)
credential_id = credential_id or client.default_credential
credential_id = credential_id or client.default_database_credential_id

script_id, run_id = _sql_script(
client,
Expand Down Expand Up @@ -485,7 +485,7 @@ def civis_to_csv(
client = APIClient()

db_id = client.get_database_id(database)
credential_id = credential_id or client.default_credential
credential_id = credential_id or client.default_database_credential_id

# don't fix bug that would cause breaking change for now
# when gzip compression is requested, a gzip file is not actually returned
Expand Down Expand Up @@ -1150,7 +1150,7 @@ def civis_file_to_table(
if schema is None:
raise ValueError("Provide a schema as part of the `table` input.")
db_id = client.get_database_id(database)
cred_id = credential_id or client.default_credential
cred_id = credential_id or client.default_database_credential_id
if delimiter is not None: # i.e. it was provided as an argument
delimiter = DELIMITERS.get(delimiter)
if not delimiter:
Expand Down Expand Up @@ -1271,7 +1271,7 @@ def _sql_script(
):
job_name = maybe_get_random_name(job_name)
db_id = client.get_database_id(database)
credential_id = credential_id or client.default_credential
credential_id = credential_id or client.default_database_credential_id
csv_settings = csv_settings or {}
sql_params_arguments = sql_params_arguments or {}

Expand Down
2 changes: 1 addition & 1 deletion src/civis/polling.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class PollableResult(CivisAsyncResultBase):
--------
>>> client = civis.APIClient()
>>> database_id = client.get_database_id("my_database")
>>> cred_id = client.default_credential
>>> cred_id = client.default_database_credential_id
>>> sql = "SELECT 1"
>>> preview_rows = 10
>>> response = client.queries.post(database_id, sql, preview_rows,
Expand Down
1 change: 1 addition & 0 deletions src/civis/resources/_client_pyi.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def generate_client_pyi(client_pyi_path, api_spec_path):
# before we can define APIClient to use them.
class APIClient:
default_credential: int | None
default_database_credential_id: int | None
username: str
feature_flags: tuple[str]
last_response: Any
Expand Down
2 changes: 1 addition & 1 deletion src/civis/resources/civis_api_spec.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/civis/tests/mocks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Mock client creation and tooling"""

import warnings
from functools import lru_cache
from unittest import mock

Expand Down Expand Up @@ -30,7 +31,9 @@ def create_client_mock(cache=None):
real_client = _real_client(cache)

# Prevent the client from trying to talk to the real API when autospeccing
with mock.patch("requests.Session", mock.MagicMock):
with mock.patch("requests.Session", mock.MagicMock), warnings.catch_warnings():
# Ignore deprecation warning from `client.default_credential`.
warnings.simplefilter("ignore", FutureWarning)
mock_client = mock.create_autospec(real_client, spec_set=True)

return mock_client
Expand Down
16 changes: 8 additions & 8 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def test_civis_file_to_table_table_exists(m_run_cleaning, m_process_cleaning_res

mock_civis.imports.post_files_csv.return_value.id = mock_import_id
mock_civis.get_database_id.return_value = 42
mock_civis.default_credential = 713
mock_civis.default_database_credential_id = 713

mock_civis.databases.get_schemas_tables.return_value = Response({"name": "table1"})
m_process_cleaning_results.return_value = (
Expand Down Expand Up @@ -288,7 +288,7 @@ def test_civis_file_to_table_table_doesnt_exist(

mock_civis.imports.post_files_csv.return_value.id = mock_import_id
mock_civis.get_database_id.return_value = 42
mock_civis.default_credential = 713
mock_civis.default_database_credential_id = 713

mock_civis.databases.get_schemas_tables.side_effect = MockAPIError(404)
mock_columns = [{"name": "foo", "sql_type": "INTEGER"}]
Expand Down Expand Up @@ -378,7 +378,7 @@ def test_civis_file_to_table_table_doesnt_exist_all_sql_types_missing(

mock_civis.imports.post_files_csv.return_value.id = mock_import_id
mock_civis.get_database_id.return_value = 42
mock_civis.default_credential = 713
mock_civis.default_database_credential_id = 713
mock_civis.databases.get_schemas_tables.side_effect = MockAPIError(404)
table_columns = [{"name": "a", "sql_type": ""}, {"name": "b", "sql_type": ""}]
detected_columns = [
Expand Down Expand Up @@ -471,7 +471,7 @@ def test_civis_file_to_table_table_does_not_exist_some_sql_types_missing(

mock_civis.imports.post_files_csv.return_value.id = mock_import_id
mock_civis.get_database_id.return_value = 42
mock_civis.default_credential = 713
mock_civis.default_database_credential_id = 713
mock_civis.databases.get_schemas_tables.side_effect = MockAPIError(404)
table_columns = [{"name": "a", "sql_type": "INT"}, {"name": "b", "sql_type": ""}]

Expand Down Expand Up @@ -503,7 +503,7 @@ def test_civis_file_to_table_table_columns_keys_misspelled(

mock_civis.imports.post_files_csv.return_value.id = mock_import_id
mock_civis.get_database_id.return_value = 42
mock_civis.default_credential = 713
mock_civis.default_database_credential_id = 713
mock_civis.databases.get_schemas_tables.side_effect = MockAPIError(404)
table_columns = [{"name": "a", "sqlType": "INT"}, {"name": "b", "bad_type": ""}]

Expand Down Expand Up @@ -541,7 +541,7 @@ def run_subtest(mock_file_ids):

mock_civis.imports.post_files_csv.return_value.id = mock_import_id
mock_civis.get_database_id.return_value = 42
mock_civis.default_credential = 713
mock_civis.default_database_credential_id = 713
mock_civis.databases.get_schemas_tables.side_effect = MockAPIError(404)
table_columns = [
{"name": "foo", "sql_type": "INTEGER"},
Expand Down Expand Up @@ -645,7 +645,7 @@ def test_civis_file_to_table_multi_file(m_run_cleaning, m_process_cleaning_resul

mock_civis.imports.post_files_csv.return_value.id = mock_import_id
mock_civis.get_database_id.return_value = 42
mock_civis.default_credential = 713
mock_civis.default_database_credential_id = 713

mock_civis.databases.get_schemas_tables.side_effect = MockAPIError(404)
mock_columns = [{"name": "foo", "sql_type": "INTEGER"}]
Expand Down Expand Up @@ -1449,7 +1449,7 @@ def test_sql_script():
mock_client = create_client_mock()
mock_client.scripts.post_sql.return_value = response
mock_client.get_database_id.return_value = database_id
mock_client.default_credential = credential_id
mock_client.default_database_credential_id = credential_id

civis.io._tables._sql_script(
client=mock_client,
Expand Down