From 95e04f9cb9f2cf816d33fd6c8dbc1a064f5fa8a4 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Wed, 2 Oct 2024 12:26:23 +0300 Subject: [PATCH 01/16] Add an ability to use both driver config and driver args --- ydb/driver.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ydb/driver.py b/ydb/driver.py index ecd3319e..d4e0ba32 100644 --- a/ydb/driver.py +++ b/ydb/driver.py @@ -214,6 +214,19 @@ def get_config( endpoint, database, root_certificates, credentials, **kwargs ) return driver_config + + if driver_config.endpoint is None and endpoint is not None: + driver_config.endpoint = endpoint + + if driver_config.database is None and database is not None: + driver_config.database = database + + if driver_config.credentials is None and credentials is not None: + driver_config.credentials = credentials + + if driver_config.root_certificates is None and root_certificates is not None: + driver_config.root_certificates = root_certificates + return driver_config From 598776ed727baffb558f01133230925f563a39f2 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Wed, 2 Oct 2024 12:36:00 +0300 Subject: [PATCH 02/16] another approach --- ydb/driver.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ydb/driver.py b/ydb/driver.py index d4e0ba32..90c55d61 100644 --- a/ydb/driver.py +++ b/ydb/driver.py @@ -190,6 +190,11 @@ def set_grpc_keep_alive_timeout(self, timeout): self.grpc_keep_alive_timeout = timeout return self + def _update_attrs_by_kwargs(self, kwargs: dict): + for key, value in kwargs.items(): + if getattr(self, key) is None: + setattr(self, key, value) + ConnectionParams = DriverConfig @@ -215,17 +220,12 @@ def get_config( ) return driver_config - if driver_config.endpoint is None and endpoint is not None: - driver_config.endpoint = endpoint - - if driver_config.database is None and database is not None: - driver_config.database = database - - if driver_config.credentials is None and credentials is not None: - driver_config.credentials = credentials + kwargs["endpoint"] = endpoint + kwargs["database"] = database + kwargs["root_certificates"] = root_certificates + kwargs["credentials"] = credentials - if driver_config.root_certificates is None and root_certificates is not None: - driver_config.root_certificates = root_certificates + driver_config._update_attrs_by_kwargs(kwargs) return driver_config From 78c3a5cc7e90e3b92d28ce32ee3ced66399a4706 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Wed, 2 Oct 2024 18:26:08 +0300 Subject: [PATCH 03/16] temp --- ydb/credentials.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/ydb/credentials.py b/ydb/credentials.py index ab502798..86913698 100644 --- a/ydb/credentials.py +++ b/ydb/credentials.py @@ -185,7 +185,45 @@ def _wrap_static_credentials_response(rpc_state, response): class StaticCredentials(AbstractExpiringTokenCredentials): def __init__(self, driver_config, user, password="", tracer=None): super(StaticCredentials, self).__init__(tracer) - self.driver_config = driver_config + + from .driver import DriverConfig + + self.driver_config = DriverConfig( + endpoint=driver_config.endpoint, + database=driver_config.database, + root_certificates=driver_config.root_certificates, + ) + self.user = user + self.password = password + self.request_timeout = 10 + + def _make_token_request(self): + conn = connection.Connection.ready_factory(self.driver_config.endpoint, self.driver_config) + assert conn is not None, "Failed to establish connection in to %s" % self.driver_config.endpoint + try: + result = conn( + ydb_auth_pb2.LoginRequest(user=self.user, password=self.password), + ydb_auth_v1_pb2_grpc.AuthServiceStub, + "Login", + _wrap_static_credentials_response, + settings_impl.BaseRequestSettings().with_timeout(self.request_timeout).with_need_rpc_auth(False), + ) + finally: + conn.close() + return {"expires_in": 30 * 60, "access_token": result.token} + + +class UserPasswordCredentials(AbstractExpiringTokenCredentials): + def __init__(self, user, password, endpoint, database, root_certificates=None, tracer=None): + super(UserPasswordCredentials).__init__(tracer) + + from .driver import DriverConfig # to prevent circular dependencies + + self.driver_config = DriverConfig( + endpoint=endpoint, + database=database, + root_certificates=root_certificates, + ) self.user = user self.password = password self.request_timeout = 10 From aaf60a8e0e4de13aa031152ec1b41ab7e7c678e6 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Thu, 3 Oct 2024 11:24:50 +0300 Subject: [PATCH 04/16] add example --- examples/static-credentials/__main__.py | 23 +++++++++ examples/static-credentials/example.py | 66 +++++++++++++++++++++++++ ydb/credentials.py | 3 +- ydb/driver.py | 20 +++++--- 4 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 examples/static-credentials/__main__.py create mode 100644 examples/static-credentials/example.py diff --git a/examples/static-credentials/__main__.py b/examples/static-credentials/__main__.py new file mode 100644 index 00000000..900396ed --- /dev/null +++ b/examples/static-credentials/__main__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +import argparse +from example import run + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description="""\033[92mStatic Credentials example.\x1b[0m\n""", + ) + parser.add_argument("-e", "--endpoint", help="Endpoint url to use", default="grpc://localhost:2136") + parser.add_argument("-d", "--database", help="Name of the database to use", default="/local") + parser.add_argument("-u", "--user", help="User to auth with", default="root") + parser.add_argument("-p", "--password", help="Password from user to auth with", default="1234") + + args = parser.parse_args() + + run( + endpoint=args.endpoint, + database=args.database, + user=args.user, + password=args.password, + ) diff --git a/examples/static-credentials/example.py b/examples/static-credentials/example.py new file mode 100644 index 00000000..96a3d15f --- /dev/null +++ b/examples/static-credentials/example.py @@ -0,0 +1,66 @@ +import ydb + + +def test_driver_works(driver: ydb.Driver): + driver.wait(5) + pool = ydb.QuerySessionPool(driver) + pool.execute_with_retries("SELECT 1") + print("everything is fine") + + +def auth_with_static_credentials_old(endpoint: str, database: str, user: str, password: str): + driver_config_temp = ydb.DriverConfig( + endpoint=endpoint, + database=database, + ) + creds = ydb.StaticCredentials( + driver_config=driver_config_temp, + user=user, + password=password, + ) + + driver_config = ydb.DriverConfig( + endpoint=endpoint, + database=database, + credentials=creds, + ) + + with ydb.Driver(driver_config=driver_config) as driver: + test_driver_works(driver) + + +def auth_with_static_credentials_new(endpoint: str, database: str, user: str, password: str): + driver_config = ydb.DriverConfig( + endpoint, + database, + ) + creds = ydb.StaticCredentials( + driver_config, + user, + password, + ) + + with ydb.Driver(driver_config=driver_config, credentials=creds) as driver: + test_driver_works(driver) + + +def auth_with_user_password_credentials(endpoint: str, database: str, user: str, password: str): + driver_config = ydb.DriverConfig( + endpoint=endpoint, + database=database, + credentials=ydb.UserPasswordCredentials( + user=user, + password=password, + endpoint=endpoint, + database=database, + ), + ) + + with ydb.Driver(driver_config=driver_config) as driver: + test_driver_works(driver) + + +def run(endpoint: str, database: str, user: str, password: str): + auth_with_static_credentials_old(endpoint, database, user, password) + auth_with_static_credentials_new(endpoint, database, user, password) + auth_with_user_password_credentials(endpoint, database, user, password) diff --git a/ydb/credentials.py b/ydb/credentials.py index 86913698..a3e054ce 100644 --- a/ydb/credentials.py +++ b/ydb/credentials.py @@ -215,7 +215,7 @@ def _make_token_request(self): class UserPasswordCredentials(AbstractExpiringTokenCredentials): def __init__(self, user, password, endpoint, database, root_certificates=None, tracer=None): - super(UserPasswordCredentials).__init__(tracer) + super(UserPasswordCredentials, self).__init__(tracer) from .driver import DriverConfig # to prevent circular dependencies @@ -224,6 +224,7 @@ def __init__(self, user, password, endpoint, database, root_certificates=None, t database=database, root_certificates=root_certificates, ) + self.user = user self.password = password self.request_timeout = 10 diff --git a/ydb/driver.py b/ydb/driver.py index 90c55d61..27dc7080 100644 --- a/ydb/driver.py +++ b/ydb/driver.py @@ -1,12 +1,16 @@ # -*- coding: utf-8 -*- +import grpc +import logging +import os +from typing import Any # noqa + from . import credentials as credentials_impl, table, scheme, pool from . import tracing -import os -import grpc from . import iam from . import _utilities -from typing import Any # noqa + +logger = logging.getLogger(__name__) class RPCCompression: @@ -190,9 +194,13 @@ def set_grpc_keep_alive_timeout(self, timeout): self.grpc_keep_alive_timeout = timeout return self - def _update_attrs_by_kwargs(self, kwargs: dict): + def _update_attrs_by_kwargs(self, **kwargs): for key, value in kwargs.items(): - if getattr(self, key) is None: + if value is not None: + if getattr(self, key) is not None: + logger.warning( + f"Arg {key} was used in both DriverConfig and Driver. Value from Driver will be used." + ) setattr(self, key, value) @@ -225,7 +233,7 @@ def get_config( kwargs["root_certificates"] = root_certificates kwargs["credentials"] = credentials - driver_config._update_attrs_by_kwargs(kwargs) + driver_config._update_attrs_by_kwargs(**kwargs) return driver_config From c41c7aebfedd84b3d86cc71d1ea8eaaa034f6c88 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Thu, 3 Oct 2024 11:28:51 +0300 Subject: [PATCH 05/16] style fixes --- ydb/driver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ydb/driver.py b/ydb/driver.py index 27dc7080..4c6b6eb5 100644 --- a/ydb/driver.py +++ b/ydb/driver.py @@ -176,7 +176,7 @@ def default_from_endpoint_and_database(cls, endpoint, database, root_certificate database, credentials=default_credentials(credentials), root_certificates=root_certificates, - **kwargs + **kwargs, ) @classmethod @@ -187,7 +187,7 @@ def default_from_connection_string(cls, connection_string, root_certificates=Non database, credentials=default_credentials(credentials), root_certificates=root_certificates, - **kwargs + **kwargs, ) def set_grpc_keep_alive_timeout(self, timeout): @@ -215,7 +215,7 @@ def get_config( root_certificates=None, credentials=None, config_class=DriverConfig, - **kwargs + **kwargs, ): if driver_config is None: if connection_string is not None: @@ -249,7 +249,7 @@ def __init__( database=None, root_certificates=None, credentials=None, - **kwargs + **kwargs, ): """ Constructs a driver instance to be used in table and scheme clients. From 30fbf409636e7e1b7cfad8840b35ae7a3c53553c Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Thu, 3 Oct 2024 17:15:50 +0300 Subject: [PATCH 06/16] update docs --- docs/examples.rst | 154 +------------------------------- docs/examples/basic_example.rst | 152 +++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 151 deletions(-) create mode 100644 docs/examples/basic_example.rst diff --git a/docs/examples.rst b/docs/examples.rst index 4f8dee84..403745d8 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,155 +1,7 @@ Examples =============== -Basic example -^^^^^^^^^^^^^ - -All examples in this section are parts of `basic example `_. - -For deeper upderstanding it is better to read the whole example. - -Create table ------------- - -.. code-block:: python - - def create_tables(pool: ydb.QuerySessionPool): - print("\nCreating table series...") - pool.execute_with_retries( - """ - CREATE table `series` ( - `series_id` Int64, - `title` Utf8, - `series_info` Utf8, - `release_date` Date, - PRIMARY KEY (`series_id`) - ) - """ - ) - - print("\nCreating table seasons...") - pool.execute_with_retries( - """ - CREATE table `seasons` ( - `series_id` Int64, - `season_id` Int64, - `title` Utf8, - `first_aired` Date, - `last_aired` Date, - PRIMARY KEY (`series_id`, `season_id`) - ) - """ - ) - - print("\nCreating table episodes...") - pool.execute_with_retries( - """ - CREATE table `episodes` ( - `series_id` Int64, - `season_id` Int64, - `episode_id` Int64, - `title` Utf8, - `air_date` Date, - PRIMARY KEY (`series_id`, `season_id`, `episode_id`) - ) - """ - ) - - -Upsert Simple -------------- - -.. code-block:: python - - def upsert_simple(pool: ydb.QuerySessionPool): - print("\nPerforming UPSERT into episodes...") - - pool.execute_with_retries( - """ - UPSERT INTO episodes (series_id, season_id, episode_id, title) VALUES (2, 6, 1, "TBD"); - """ - ) - - -Simple Select ----------- - -.. code-block:: python - - def select_simple(pool: ydb.QuerySessionPool): - print("\nCheck series table...") - result_sets = pool.execute_with_retries( - """ - SELECT - series_id, - title, - release_date - FROM series - WHERE series_id = 1; - """, - ) - first_set = result_sets[0] - for row in first_set.rows: - print( - "series, id: ", - row.series_id, - ", title: ", - row.title, - ", release date: ", - row.release_date, - ) - - return first_set - -Select With Parameters ----------------------- - -.. code-block:: python - - def select_with_parameters(pool: ydb.QuerySessionPool, series_id, season_id, episode_id): - result_sets = pool.execute_with_retries( - """ - DECLARE $seriesId AS Int64; - DECLARE $seasonId AS Int64; - DECLARE $episodeId AS Int64; - - SELECT - title, - air_date - FROM episodes - WHERE series_id = $seriesId AND season_id = $seasonId AND episode_id = $episodeId; - """, - { - "$seriesId": series_id, # could be defined implicit - "$seasonId": (season_id, ydb.PrimitiveType.Int64), # could be defined via tuple - "$episodeId": ydb.TypedValue(episode_id, ydb.PrimitiveType.Int64), # could be defined via special class - }, - ) - - print("\n> select_with_parameters:") - first_set = result_sets[0] - for row in first_set.rows: - print("episode title:", row.title, ", air date:", row.air_date) - - return first_set - -Huge Select ------------ - -.. code-block:: python - - def huge_select(pool: ydb.QuerySessionPool): - def callee(session: ydb.QuerySessionSync): - query = """SELECT * from episodes;""" - - with session.transaction().execute( - query, - commit_tx=True, - ) as result_sets: - print("\n> Huge SELECT call") - for result_set in result_sets: - for row in result_set.rows: - print("episode title:", row.title, ", air date:", row.air_date) - - return pool.retry_operation_sync(callee) +.. toctree:: + :maxdepth: 3 + examples/basic_example \ No newline at end of file diff --git a/docs/examples/basic_example.rst b/docs/examples/basic_example.rst new file mode 100644 index 00000000..e7f38737 --- /dev/null +++ b/docs/examples/basic_example.rst @@ -0,0 +1,152 @@ +Basic example +============= + +All examples in this section are parts of `basic example `_. + +For deeper upderstanding it is better to read the whole example. + +Create table +------------ + +.. code-block:: python + + def create_tables(pool: ydb.QuerySessionPool): + print("\nCreating table series...") + pool.execute_with_retries( + """ + CREATE table `series` ( + `series_id` Int64, + `title` Utf8, + `series_info` Utf8, + `release_date` Date, + PRIMARY KEY (`series_id`) + ) + """ + ) + + print("\nCreating table seasons...") + pool.execute_with_retries( + """ + CREATE table `seasons` ( + `series_id` Int64, + `season_id` Int64, + `title` Utf8, + `first_aired` Date, + `last_aired` Date, + PRIMARY KEY (`series_id`, `season_id`) + ) + """ + ) + + print("\nCreating table episodes...") + pool.execute_with_retries( + """ + CREATE table `episodes` ( + `series_id` Int64, + `season_id` Int64, + `episode_id` Int64, + `title` Utf8, + `air_date` Date, + PRIMARY KEY (`series_id`, `season_id`, `episode_id`) + ) + """ + ) + + +Upsert Simple +------------- + +.. code-block:: python + + def upsert_simple(pool: ydb.QuerySessionPool): + print("\nPerforming UPSERT into episodes...") + + pool.execute_with_retries( + """ + UPSERT INTO episodes (series_id, season_id, episode_id, title) VALUES (2, 6, 1, "TBD"); + """ + ) + + +Simple Select +---------- + +.. code-block:: python + + def select_simple(pool: ydb.QuerySessionPool): + print("\nCheck series table...") + result_sets = pool.execute_with_retries( + """ + SELECT + series_id, + title, + release_date + FROM series + WHERE series_id = 1; + """, + ) + first_set = result_sets[0] + for row in first_set.rows: + print( + "series, id: ", + row.series_id, + ", title: ", + row.title, + ", release date: ", + row.release_date, + ) + + return first_set + +Select With Parameters +---------------------- + +.. code-block:: python + + def select_with_parameters(pool: ydb.QuerySessionPool, series_id, season_id, episode_id): + result_sets = pool.execute_with_retries( + """ + DECLARE $seriesId AS Int64; + DECLARE $seasonId AS Int64; + DECLARE $episodeId AS Int64; + + SELECT + title, + air_date + FROM episodes + WHERE series_id = $seriesId AND season_id = $seasonId AND episode_id = $episodeId; + """, + { + "$seriesId": series_id, # could be defined implicit + "$seasonId": (season_id, ydb.PrimitiveType.Int64), # could be defined via tuple + "$episodeId": ydb.TypedValue(episode_id, ydb.PrimitiveType.Int64), # could be defined via special class + }, + ) + + print("\n> select_with_parameters:") + first_set = result_sets[0] + for row in first_set.rows: + print("episode title:", row.title, ", air date:", row.air_date) + + return first_set + +Huge Select +----------- + +.. code-block:: python + + def huge_select(pool: ydb.QuerySessionPool): + def callee(session: ydb.QuerySessionSync): + query = """SELECT * from episodes;""" + + with session.transaction().execute( + query, + commit_tx=True, + ) as result_sets: + print("\n> Huge SELECT call") + for result_set in result_sets: + for row in result_set.rows: + print("episode title:", row.title, ", air date:", row.air_date) + + return pool.retry_operation_sync(callee) + From d36e407c43a103fe5785336a4fa1abc51f56c745 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Thu, 3 Oct 2024 17:17:31 +0300 Subject: [PATCH 07/16] temporary allow to publish docs from branch --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 07b7645f..82bb9a71 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,7 +2,7 @@ name: Deploy Sphinx documentation to Github Pages on: push: - branches: [main] # branch to trigger deployment + branches: [main, fix_driver_and_driver_config_collision] # branch to trigger deployment jobs: pages: From f24ae46cf2318d383423d1afd62b65e1b87ef0d4 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Thu, 3 Oct 2024 17:41:38 +0300 Subject: [PATCH 08/16] Describe all authentication methods in docs --- docs/examples.rst | 3 +- docs/examples/authentication.rst | 133 +++++++++++++++++++++++++ examples/static-credentials/example.py | 10 +- 3 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 docs/examples/authentication.rst diff --git a/docs/examples.rst b/docs/examples.rst index 403745d8..8d7cbf3c 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -4,4 +4,5 @@ Examples .. toctree:: :maxdepth: 3 - examples/basic_example \ No newline at end of file + examples/basic_example + examples/authentication \ No newline at end of file diff --git a/docs/examples/authentication.rst b/docs/examples/authentication.rst new file mode 100644 index 00000000..02fffddd --- /dev/null +++ b/docs/examples/authentication.rst @@ -0,0 +1,133 @@ +Authentication +============== + +There are several ways to authenticate through YDB Python SDK. + +Anonymous Credentials +--------------------- + +Full executable example `here `_. + +.. code-block:: python + + driver = ydb.Driver( + endpoint=os.getenv("YDB_ENDPOINT"), + database=os.getenv("YDB_DATABASE"), + credentials=ydb.AnonymousCredentials(), + ) + + +Access Token Credentials +------------------------ + +Full executable example `here `_. + +.. code-block:: python + + driver = ydb.Driver( + endpoint=os.getenv("YDB_ENDPOINT"), + database=os.getenv("YDB_DATABASE"), + credentials=ydb.AccessTokenCredentials(os.getenv("YDB_ACCESS_TOKEN_CREDENTIALS")), + ) + + +Static Credentials (Legacy) +--------------------------- +This method is legacy, use UserPasswordCredentials instead. + +Full executable example `here `_. + + +.. code-block:: python + + driver_config = ydb.DriverConfig( + endpoint=endpoint, + database=database, + ) + creds = ydb.StaticCredentials( + driver_config=driver_config, + user=user, + password=password, + ) + + driver = ydb.Driver( + driver_config=driver_config, + credentials=creds, + ) + + +User Password Credentials +------------------------- + +Full executable example `here `_. + +.. code-block:: python + + driver_config = ydb.DriverConfig( + endpoint=endpoint, + database=database, + credentials=ydb.UserPasswordCredentials( + user=user, + password=password, + endpoint=endpoint, + database=database, + ), + ) + + driver = ydb.Driver(driver_config=driver_config) + + +Service Accaount Credentials +---------------------------- + +Full executable example `here `_. + +.. code-block:: python + + driver = ydb.Driver( + endpoint=os.getenv("YDB_ENDPOINT"), + database=os.getenv("YDB_DATABASE"), + credentials=ydb.iam.ServiceAccountCredentials.from_file( + os.getenv("SA_KEY_FILE"), + ), + ) + + +OAuth 2.0 Token Exchange Credentials +------------------------------------ + +Full executable example `here `_. + +.. code-block:: python + + driver = ydb.Driver( + endpoint=args.endpoint, + database=args.database, + root_certificates=ydb.load_ydb_root_certificate(), + credentials=ydb.oauth2_token_exchange.Oauth2TokenExchangeCredentials( + token_endpoint=args.token_endpoint, + audience=args.audience, + subject_token_source=ydb.oauth2_token_exchange.JwtTokenSource( + signing_method="RS256", + private_key_file=args.private_key_file, + key_id=args.key_id, + issuer=args.issuer, + subject=args.subject, + audience=args.audience, + ), + ), + ) + + +Metadata Credentials +-------------------- + +Full executable example `here `_. + +.. code-block:: python + + driver = ydb.Driver( + endpoint=os.getenv("YDB_ENDPOINT"), + database=os.getenv("YDB_DATABASE"), + credentials=ydb.iam.MetadataUrlCredentials(), + ) diff --git a/examples/static-credentials/example.py b/examples/static-credentials/example.py index 96a3d15f..e45da5a3 100644 --- a/examples/static-credentials/example.py +++ b/examples/static-credentials/example.py @@ -31,13 +31,13 @@ def auth_with_static_credentials_old(endpoint: str, database: str, user: str, pa def auth_with_static_credentials_new(endpoint: str, database: str, user: str, password: str): driver_config = ydb.DriverConfig( - endpoint, - database, + endpoint=endpoint, + database=database, ) creds = ydb.StaticCredentials( - driver_config, - user, - password, + driver_config=driver_config, + user=user, + password=password, ) with ydb.Driver(driver_config=driver_config, credentials=creds) as driver: From e3a2f98dfc5ffebe620e01ea4937e857684a2077 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Fri, 4 Oct 2024 14:05:13 +0300 Subject: [PATCH 09/16] new way to create static credentials --- examples/static-credentials/example.py | 12 ++++++++++++ ydb/credentials.py | 24 +++++++++++++++++++----- ydb/driver.py | 14 ++++++++------ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/examples/static-credentials/example.py b/examples/static-credentials/example.py index e45da5a3..d269da60 100644 --- a/examples/static-credentials/example.py +++ b/examples/static-credentials/example.py @@ -44,6 +44,17 @@ def auth_with_static_credentials_new(endpoint: str, database: str, user: str, pa test_driver_works(driver) +def auth_with_static_credentials_new2(endpoint: str, database: str, user: str, password: str): + driver_config = ydb.DriverConfig( + endpoint=endpoint, + database=database, + credentials=ydb.StaticCredentials.from_user_password(user, password), + ) + + with ydb.Driver(driver_config=driver_config) as driver: + test_driver_works(driver) + + def auth_with_user_password_credentials(endpoint: str, database: str, user: str, password: str): driver_config = ydb.DriverConfig( endpoint=endpoint, @@ -63,4 +74,5 @@ def auth_with_user_password_credentials(endpoint: str, database: str, user: str, def run(endpoint: str, database: str, user: str, password: str): auth_with_static_credentials_old(endpoint, database, user, password) auth_with_static_credentials_new(endpoint, database, user, password) + auth_with_static_credentials_new2(endpoint, database, user, password) auth_with_user_password_credentials(endpoint, database, user, password) diff --git a/ydb/credentials.py b/ydb/credentials.py index a3e054ce..7b823b25 100644 --- a/ydb/credentials.py +++ b/ydb/credentials.py @@ -188,15 +188,20 @@ def __init__(self, driver_config, user, password="", tracer=None): from .driver import DriverConfig - self.driver_config = DriverConfig( - endpoint=driver_config.endpoint, - database=driver_config.database, - root_certificates=driver_config.root_certificates, - ) + if driver_config is not None: + self.driver_config = DriverConfig( + endpoint=driver_config.endpoint, + database=driver_config.database, + root_certificates=driver_config.root_certificates, + ) self.user = user self.password = password self.request_timeout = 10 + @classmethod + def from_user_password(cls, user: str, password: str, tracer=None): + return cls(None, user, password, tracer) + def _make_token_request(self): conn = connection.Connection.ready_factory(self.driver_config.endpoint, self.driver_config) assert conn is not None, "Failed to establish connection in to %s" % self.driver_config.endpoint @@ -212,6 +217,15 @@ def _make_token_request(self): conn.close() return {"expires_in": 30 * 60, "access_token": result.token} + def _update_driver_config(self, driver_config): + from .driver import DriverConfig + + self.driver_config = DriverConfig( + endpoint=driver_config.endpoint, + database=driver_config.database, + root_certificates=driver_config.root_certificates, + ) + class UserPasswordCredentials(AbstractExpiringTokenCredentials): def __init__(self, user, password, endpoint, database, root_certificates=None, tracer=None): diff --git a/ydb/driver.py b/ydb/driver.py index 4c6b6eb5..b811bd76 100644 --- a/ydb/driver.py +++ b/ydb/driver.py @@ -226,14 +226,16 @@ def get_config( driver_config = config_class.default_from_endpoint_and_database( endpoint, database, root_certificates, credentials, **kwargs ) - return driver_config + else: + kwargs["endpoint"] = endpoint + kwargs["database"] = database + kwargs["root_certificates"] = root_certificates + kwargs["credentials"] = credentials - kwargs["endpoint"] = endpoint - kwargs["database"] = database - kwargs["root_certificates"] = root_certificates - kwargs["credentials"] = credentials + driver_config._update_attrs_by_kwargs(**kwargs) - driver_config._update_attrs_by_kwargs(**kwargs) + if isinstance(driver_config.credentials, credentials_impl.StaticCredentials): + driver_config.credentials._update_driver_config(driver_config) return driver_config From dff73035a2f4fccb3b2f6dd00342579afd943f47 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Fri, 4 Oct 2024 16:05:20 +0300 Subject: [PATCH 10/16] make _update_driver_config common method to all credentials --- ydb/credentials.py | 3 +++ ydb/driver.py | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ydb/credentials.py b/ydb/credentials.py index 7b823b25..882ea6a1 100644 --- a/ydb/credentials.py +++ b/ydb/credentials.py @@ -45,6 +45,9 @@ def get_auth_token(self) -> str: return token return "" + def _update_driver_config(self, driver_config): + pass + class OneToManyValue(object): def __init__(self): diff --git a/ydb/driver.py b/ydb/driver.py index b811bd76..0e59301b 100644 --- a/ydb/driver.py +++ b/ydb/driver.py @@ -234,8 +234,7 @@ def get_config( driver_config._update_attrs_by_kwargs(**kwargs) - if isinstance(driver_config.credentials, credentials_impl.StaticCredentials): - driver_config.credentials._update_driver_config(driver_config) + driver_config.credentials._update_driver_config(driver_config) return driver_config From 257f5c42bf618360785e91c9be217c9edd389496 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Fri, 4 Oct 2024 16:35:25 +0300 Subject: [PATCH 11/16] remove UserPasswordCredentials --- docs/examples/authentication.rst | 35 ++------------ examples/static-credentials/example.py | 63 ++------------------------ ydb/credentials.py | 32 ------------- 3 files changed, 7 insertions(+), 123 deletions(-) diff --git a/docs/examples/authentication.rst b/docs/examples/authentication.rst index 02fffddd..9f489172 100644 --- a/docs/examples/authentication.rst +++ b/docs/examples/authentication.rst @@ -31,9 +31,8 @@ Full executable example `here `_. @@ -43,41 +42,13 @@ Full executable example `here `_. - -.. code-block:: python - - driver_config = ydb.DriverConfig( - endpoint=endpoint, - database=database, - credentials=ydb.UserPasswordCredentials( - user=user, - password=password, - endpoint=endpoint, - database=database, - ), + credentials=ydb.StaticCredentials.from_user_password(user, password), ) driver = ydb.Driver(driver_config=driver_config) -Service Accaount Credentials +Service Account Credentials ---------------------------- Full executable example `here `_. diff --git a/examples/static-credentials/example.py b/examples/static-credentials/example.py index d269da60..71409f5c 100644 --- a/examples/static-credentials/example.py +++ b/examples/static-credentials/example.py @@ -4,47 +4,11 @@ def test_driver_works(driver: ydb.Driver): driver.wait(5) pool = ydb.QuerySessionPool(driver) - pool.execute_with_retries("SELECT 1") - print("everything is fine") + result = pool.execute_with_retries("SELECT 1 as cnt") + assert result[0].rows[0].cnt == 1 -def auth_with_static_credentials_old(endpoint: str, database: str, user: str, password: str): - driver_config_temp = ydb.DriverConfig( - endpoint=endpoint, - database=database, - ) - creds = ydb.StaticCredentials( - driver_config=driver_config_temp, - user=user, - password=password, - ) - - driver_config = ydb.DriverConfig( - endpoint=endpoint, - database=database, - credentials=creds, - ) - - with ydb.Driver(driver_config=driver_config) as driver: - test_driver_works(driver) - - -def auth_with_static_credentials_new(endpoint: str, database: str, user: str, password: str): - driver_config = ydb.DriverConfig( - endpoint=endpoint, - database=database, - ) - creds = ydb.StaticCredentials( - driver_config=driver_config, - user=user, - password=password, - ) - - with ydb.Driver(driver_config=driver_config, credentials=creds) as driver: - test_driver_works(driver) - - -def auth_with_static_credentials_new2(endpoint: str, database: str, user: str, password: str): +def auth_with_static_credentials(endpoint: str, database: str, user: str, password: str): driver_config = ydb.DriverConfig( endpoint=endpoint, database=database, @@ -55,24 +19,5 @@ def auth_with_static_credentials_new2(endpoint: str, database: str, user: str, p test_driver_works(driver) -def auth_with_user_password_credentials(endpoint: str, database: str, user: str, password: str): - driver_config = ydb.DriverConfig( - endpoint=endpoint, - database=database, - credentials=ydb.UserPasswordCredentials( - user=user, - password=password, - endpoint=endpoint, - database=database, - ), - ) - - with ydb.Driver(driver_config=driver_config) as driver: - test_driver_works(driver) - - def run(endpoint: str, database: str, user: str, password: str): - auth_with_static_credentials_old(endpoint, database, user, password) - auth_with_static_credentials_new(endpoint, database, user, password) - auth_with_static_credentials_new2(endpoint, database, user, password) - auth_with_user_password_credentials(endpoint, database, user, password) + auth_with_static_credentials(endpoint, database, user, password) diff --git a/ydb/credentials.py b/ydb/credentials.py index 882ea6a1..ab721d0b 100644 --- a/ydb/credentials.py +++ b/ydb/credentials.py @@ -230,38 +230,6 @@ def _update_driver_config(self, driver_config): ) -class UserPasswordCredentials(AbstractExpiringTokenCredentials): - def __init__(self, user, password, endpoint, database, root_certificates=None, tracer=None): - super(UserPasswordCredentials, self).__init__(tracer) - - from .driver import DriverConfig # to prevent circular dependencies - - self.driver_config = DriverConfig( - endpoint=endpoint, - database=database, - root_certificates=root_certificates, - ) - - self.user = user - self.password = password - self.request_timeout = 10 - - def _make_token_request(self): - conn = connection.Connection.ready_factory(self.driver_config.endpoint, self.driver_config) - assert conn is not None, "Failed to establish connection in to %s" % self.driver_config.endpoint - try: - result = conn( - ydb_auth_pb2.LoginRequest(user=self.user, password=self.password), - ydb_auth_v1_pb2_grpc.AuthServiceStub, - "Login", - _wrap_static_credentials_response, - settings_impl.BaseRequestSettings().with_timeout(self.request_timeout).with_need_rpc_auth(False), - ) - finally: - conn.close() - return {"expires_in": 30 * 60, "access_token": result.token} - - class AnonymousCredentials(Credentials): @staticmethod def auth_metadata(): From ae3f8f913353b2d5a29929caf9b148df86c60aa7 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Fri, 4 Oct 2024 16:54:14 +0300 Subject: [PATCH 12/16] add unit tests to static credentials --- tests/auth/test_static_credentials.py | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/auth/test_static_credentials.py diff --git a/tests/auth/test_static_credentials.py b/tests/auth/test_static_credentials.py new file mode 100644 index 00000000..c6d2d640 --- /dev/null +++ b/tests/auth/test_static_credentials.py @@ -0,0 +1,50 @@ +import pytest +import ydb + +from concurrent.futures import TimeoutError + + +USERNAME = "root" +PASSWORD = "1234" + + +def check_driver_works(driver): + driver.wait(timeout=15) + pool = ydb.QuerySessionPool(driver) + result = pool.execute_with_retries("SELECT 1 as cnt") + assert result[0].rows[0].cnt == 1 + + +def test_static_credentials_default(endpoint, database): + driver_config = ydb.DriverConfig( + endpoint, + database + ) + credentials = ydb.StaticCredentials(driver_config, USERNAME, PASSWORD) + + with ydb.Driver(driver_config=driver_config, credentials=credentials) as driver: + check_driver_works(driver) + + +def test_static_credentials_classmethod(endpoint, database): + driver_config = ydb.DriverConfig( + endpoint, + database, + credentials=ydb.StaticCredentials.from_user_password(USERNAME, PASSWORD) + ) + + with ydb.Driver(driver_config=driver_config) as driver: + check_driver_works(driver) + + +def test_static_credentials_wrong_creds(endpoint, database): + driver_config = ydb.DriverConfig( + endpoint, + database, + credentials=ydb.StaticCredentials.from_user_password(USERNAME, PASSWORD*2) + ) + + with pytest.raises(TimeoutError): + with ydb.Driver(driver_config=driver_config) as driver: + driver.wait(5) + From ff72d74c037be213d90a491b276a51becf98e6aa Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Fri, 4 Oct 2024 16:56:15 +0300 Subject: [PATCH 13/16] style fixes --- tests/auth/test_static_credentials.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/auth/test_static_credentials.py b/tests/auth/test_static_credentials.py index c6d2d640..4371c460 100644 --- a/tests/auth/test_static_credentials.py +++ b/tests/auth/test_static_credentials.py @@ -18,7 +18,7 @@ def check_driver_works(driver): def test_static_credentials_default(endpoint, database): driver_config = ydb.DriverConfig( endpoint, - database + database, ) credentials = ydb.StaticCredentials(driver_config, USERNAME, PASSWORD) @@ -30,7 +30,7 @@ def test_static_credentials_classmethod(endpoint, database): driver_config = ydb.DriverConfig( endpoint, database, - credentials=ydb.StaticCredentials.from_user_password(USERNAME, PASSWORD) + credentials=ydb.StaticCredentials.from_user_password(USERNAME, PASSWORD), ) with ydb.Driver(driver_config=driver_config) as driver: @@ -41,10 +41,9 @@ def test_static_credentials_wrong_creds(endpoint, database): driver_config = ydb.DriverConfig( endpoint, database, - credentials=ydb.StaticCredentials.from_user_password(USERNAME, PASSWORD*2) + credentials=ydb.StaticCredentials.from_user_password(USERNAME, PASSWORD*2), ) with pytest.raises(TimeoutError): with ydb.Driver(driver_config=driver_config) as driver: driver.wait(5) - From 4675d5767a17c73a521ce5d00e860cac8010c565 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Fri, 4 Oct 2024 16:59:14 +0300 Subject: [PATCH 14/16] fix test --- tests/auth/test_static_credentials.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/auth/test_static_credentials.py b/tests/auth/test_static_credentials.py index 4371c460..a9239f2a 100644 --- a/tests/auth/test_static_credentials.py +++ b/tests/auth/test_static_credentials.py @@ -1,8 +1,6 @@ import pytest import ydb -from concurrent.futures import TimeoutError - USERNAME = "root" PASSWORD = "1234" @@ -41,9 +39,9 @@ def test_static_credentials_wrong_creds(endpoint, database): driver_config = ydb.DriverConfig( endpoint, database, - credentials=ydb.StaticCredentials.from_user_password(USERNAME, PASSWORD*2), + credentials=ydb.StaticCredentials.from_user_password(USERNAME, PASSWORD * 2), ) - with pytest.raises(TimeoutError): + with pytest.raises(ydb.ConnectionFailure): with ydb.Driver(driver_config=driver_config) as driver: - driver.wait(5) + driver.wait(5, fail_fast=True) From df79b1766e090065feac0c7e0ab2cbf32e5320c7 Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Fri, 4 Oct 2024 19:04:27 +0300 Subject: [PATCH 15/16] Update driver.py --- ydb/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ydb/driver.py b/ydb/driver.py index 0e59301b..1559b0d0 100644 --- a/ydb/driver.py +++ b/ydb/driver.py @@ -234,7 +234,8 @@ def get_config( driver_config._update_attrs_by_kwargs(**kwargs) - driver_config.credentials._update_driver_config(driver_config) + if driver_config.credentials is not None: + driver_config.credentials._update_driver_config(driver_config) return driver_config From eaf6807dc67d1691a4f3db1b85e3fdd139e30b9c Mon Sep 17 00:00:00 2001 From: Oleg Ovcharuk Date: Fri, 4 Oct 2024 19:35:24 +0300 Subject: [PATCH 16/16] Update docs.yml --- .github/workflows/docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 82bb9a71..568e881f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,7 +2,7 @@ name: Deploy Sphinx documentation to Github Pages on: push: - branches: [main, fix_driver_and_driver_config_collision] # branch to trigger deployment + branches: [main] # branch to trigger deployment jobs: pages: @@ -15,4 +15,4 @@ jobs: id-token: write steps: - id: deployment - uses: sphinx-notes/pages@v3 \ No newline at end of file + uses: sphinx-notes/pages@v3