From f18d0e161abb60b30826f65bf8bfa98ab733021e Mon Sep 17 00:00:00 2001 From: rahul yadav Date: Fri, 23 Feb 2024 10:30:11 +0530 Subject: [PATCH] incorporate suggestions --- samples/samples/admin/pg_samples.py | 248 ++++++++++++++++++++++- samples/samples/admin/pg_samples_test.py | 61 +++++- samples/samples/admin/samples.py | 122 ++++++++++- samples/samples/admin/samples_test.py | 29 +++ 4 files changed, 452 insertions(+), 8 deletions(-) diff --git a/samples/samples/admin/pg_samples.py b/samples/samples/admin/pg_samples.py index 042378a475..4da2cafc33 100644 --- a/samples/samples/admin/pg_samples.py +++ b/samples/samples/admin/pg_samples.py @@ -19,16 +19,74 @@ For more information, see the README.rst under /spanner. """ from google.cloud import spanner -from google.cloud.spanner_admin_database_v1.types import spanner_database_admin +from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect OPERATION_TIMEOUT_SECONDS = 240 +# [START spanner_postgresql_create_database] +def create_database(instance_id, database_id): + """Creates a PostgreSql database and tables for sample data.""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + + request = spanner_database_admin.CreateDatabaseRequest( + parent=instance.name, + create_statement=f'CREATE DATABASE "{database_id}"', + database_dialect=DatabaseDialect.POSTGRESQL, + ) + + operation = spanner_client.database_admin_api.create_database(request=request) + + print("Waiting for operation to complete...") + database = operation.result(OPERATION_TIMEOUT_SECONDS) + + create_table_using_ddl(database.name) + print("Created database {} on instance {}".format(database_id, instance_id)) + + +def create_table_using_ddl(database_name): + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + request = spanner_database_admin.UpdateDatabaseDdlRequest( + database=database_name, + statements=[ + """CREATE TABLE Singers ( + SingerId bigint NOT NULL, + FirstName character varying(1024), + LastName character varying(1024), + SingerInfo bytea, + FullName character varying(2048) + GENERATED ALWAYS AS (FirstName || ' ' || LastName) STORED, + PRIMARY KEY (SingerId) + )""", + """CREATE TABLE Albums ( + SingerId bigint NOT NULL, + AlbumId bigint NOT NULL, + AlbumTitle character varying(1024), + PRIMARY KEY (SingerId, AlbumId) + ) INTERLEAVE IN PARENT Singers ON DELETE CASCADE""", + ], + ) + operation = spanner_client.database_admin_api.update_database_ddl(request) + operation.result(OPERATION_TIMEOUT_SECONDS) + + +# [END spanner_postgresql_create_database] + + def create_table_with_datatypes(instance_id, database_id): """Creates a table with supported datatypes.""" # [START spanner_postgresql_create_table_with_datatypes] # instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -61,6 +119,31 @@ def create_table_with_datatypes(instance_id, database_id): # [END spanner_postgresql_create_table_with_datatypes] +# [START spanner_postgresql_add_column] +def add_column(instance_id, database_id): + """Adds a new column to the Albums table in the example database.""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + request = spanner_database_admin.UpdateDatabaseDdlRequest( + database=database.name, + statements=["ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT"], + ) + operation = spanner_client.database_admin_api.update_database_ddl(request) + + print("Waiting for operation to complete...") + operation.result(OPERATION_TIMEOUT_SECONDS) + + print("Added the MarketingBudget column.") + + +# [END spanner_postgresql_add_column] + + # [START spanner_postgresql_jsonb_add_column] def add_jsonb_column(instance_id, database_id): """ @@ -81,6 +164,8 @@ def add_jsonb_column(instance_id, database_id): # instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -103,3 +188,164 @@ def add_jsonb_column(instance_id, database_id): # [END spanner_postgresql_jsonb_add_column] + + +# [START spanner_postgresql_create_storing_index] +def add_storing_index(instance_id, database_id): + """Adds an storing index to the example database.""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + request = spanner_database_admin.UpdateDatabaseDdlRequest( + database=database.name, + statements=[ + "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle)" + "INCLUDE (MarketingBudget)" + ], + ) + + operation = spanner_client.database_admin_api.update_database_ddl(request) + + print("Waiting for operation to complete...") + operation.result(OPERATION_TIMEOUT_SECONDS) + + print("Added the AlbumsByAlbumTitle2 index.") + + +# [END spanner_postgresql_create_storing_index] + + +# [START spanner_postgresql_create_sequence] +def create_sequence(instance_id, database_id): + """Creates the Sequence and insert data""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + request = spanner_database_admin.UpdateDatabaseDdlRequest( + database=database.name, + statements=[ + "CREATE SEQUENCE Seq BIT_REVERSED_POSITIVE", + """CREATE TABLE Customers ( + CustomerId BIGINT DEFAULT nextval('Seq'), + CustomerName character varying(1024), + PRIMARY KEY (CustomerId) + )""", + ], + ) + operation = spanner_client.database_admin_api.update_database_ddl(request) + print("Waiting for operation to complete...") + operation.result(OPERATION_TIMEOUT_SECONDS) + + print( + "Created Seq sequence and Customers table, where the key column CustomerId uses the sequence as a default value on database {} on instance {}".format( + database_id, instance_id + ) + ) + + def insert_customers(transaction): + results = transaction.execute_sql( + "INSERT INTO Customers (CustomerName) VALUES " + "('Alice'), " + "('David'), " + "('Marc') " + "RETURNING CustomerId" + ) + for result in results: + print("Inserted customer record with Customer Id: {}".format(*result)) + print( + "Number of customer records inserted is {}".format( + results.stats.row_count_exact + ) + ) + + database.run_in_transaction(insert_customers) + + +# [END spanner_postgresql_create_sequence] + + +# [START spanner_postgresql_alter_sequence] +def alter_sequence(instance_id, database_id): + """Alters the Sequence and insert data""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + request = spanner_database_admin.UpdateDatabaseDdlRequest( + database=database.name, + statements=["ALTER SEQUENCE Seq SKIP RANGE 1000 5000000"], + ) + operation = spanner_client.database_admin_api.update_database_ddl(request) + + print("Waiting for operation to complete...") + operation.result(OPERATION_TIMEOUT_SECONDS) + + print( + "Altered Seq sequence to skip an inclusive range between 1000 and 5000000 on database {} on instance {}".format( + database_id, instance_id + ) + ) + + def insert_customers(transaction): + results = transaction.execute_sql( + "INSERT INTO Customers (CustomerName) VALUES " + "('Lea'), " + "('Cataline'), " + "('Smith') " + "RETURNING CustomerId" + ) + for result in results: + print("Inserted customer record with Customer Id: {}".format(*result)) + print( + "Number of customer records inserted is {}".format( + results.stats.row_count_exact + ) + ) + + database.run_in_transaction(insert_customers) + + +# [END spanner_postgresql_alter_sequence] + + +# [START spanner_postgresql_drop_sequence] +def drop_sequence(instance_id, database_id): + """Drops the Sequence""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + request = spanner_database_admin.UpdateDatabaseDdlRequest( + database=database.name, + statements=[ + "ALTER TABLE Customers ALTER COLUMN CustomerId DROP DEFAULT", + "DROP SEQUENCE Seq", + ], + ) + operation = spanner_client.database_admin_api.update_database_ddl(request) + + print("Waiting for operation to complete...") + operation.result(OPERATION_TIMEOUT_SECONDS) + + print( + "Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence on database {} on instance {}".format( + database_id, instance_id + ) + ) + + +# [END spanner_postgresql_drop_sequence] diff --git a/samples/samples/admin/pg_samples_test.py b/samples/samples/admin/pg_samples_test.py index 9fd30c94d3..3863f5aa56 100644 --- a/samples/samples/admin/pg_samples_test.py +++ b/samples/samples/admin/pg_samples_test.py @@ -104,6 +104,15 @@ def default_leader(): return "us-east4" +@pytest.mark.dependency(name="create_database") +def test_create_database_explicit(sample_instance, create_database_id): + # Rather than re-use 'sample_database', we create a new database, to + # ensure that the 'create_database' snippet is tested. + samples.create_database(sample_instance.instance_id, create_database_id) + database = sample_instance.database(create_database_id) + database.drop() + + @pytest.mark.dependency(name="create_table_with_datatypes") def test_create_table_with_datatypes(capsys, instance_id, sample_database): samples.create_table_with_datatypes(instance_id, sample_database.database_id) @@ -111,9 +120,59 @@ def test_create_table_with_datatypes(capsys, instance_id, sample_database): assert "Created Venues table on database" in out -@pytest.mark.dependency(name="add_jsonb_column", depends=["insert_datatypes_data"]) +@pytest.mark.dependency(name="add_column", depends=["create_database"]) +def test_add_column(capsys, instance_id, sample_database): + samples.add_column(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert "Added the MarketingBudget column." in out + + +@pytest.mark.dependency(name="add_storing_index", depends=["create_database"]) +def test_add_storing_index(capsys, instance_id, sample_database): + samples.add_storing_index(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert "Added the AlbumsByAlbumTitle2 index." in out + + +@pytest.mark.dependency( + name="add_jsonb_column", depends=["create_table_with_datatypes"] +) def test_add_jsonb_column(capsys, instance_id, sample_database): samples.add_jsonb_column(instance_id, sample_database.database_id) out, _ = capsys.readouterr() assert "Waiting for operation to complete..." in out assert 'Altered table "Venues" on database ' in out + + +@pytest.mark.dependency(name="create_sequence") +def test_create_sequence(capsys, instance_id, bit_reverse_sequence_database): + samples.create_sequence(instance_id, bit_reverse_sequence_database.database_id) + out, _ = capsys.readouterr() + assert ( + "Created Seq sequence and Customers table, where the key column CustomerId uses the sequence as a default value on database" + in out + ) + assert "Number of customer records inserted is 3" in out + assert "Inserted customer record with Customer Id:" in out + + +@pytest.mark.dependency(name="alter_sequence", depends=["create_sequence"]) +def test_alter_sequence(capsys, instance_id, bit_reverse_sequence_database): + samples.alter_sequence(instance_id, bit_reverse_sequence_database.database_id) + out, _ = capsys.readouterr() + assert ( + "Altered Seq sequence to skip an inclusive range between 1000 and 5000000 on database" + in out + ) + assert "Number of customer records inserted is 3" in out + assert "Inserted customer record with Customer Id:" in out + + +@pytest.mark.dependency(depends=["alter_sequence"]) +def test_drop_sequence(capsys, instance_id, bit_reverse_sequence_database): + samples.drop_sequence(instance_id, bit_reverse_sequence_database.database_id) + out, _ = capsys.readouterr() + assert ( + "Altered Customers table to drop DEFAULT from CustomerId column and dropped the Seq sequence on database" + in out + ) diff --git a/samples/samples/admin/samples.py b/samples/samples/admin/samples.py index 99e8e4822a..2c2ca516a3 100644 --- a/samples/samples/admin/samples.py +++ b/samples/samples/admin/samples.py @@ -22,12 +22,6 @@ import time from google.cloud import spanner -from google.type import expr_pb2 -from google.iam.v1 import iam_policy_pb2 -from google.iam.v1 import options_pb2 -from google.iam.v1 import policy_pb2 -from google.cloud.spanner_admin_database_v1.types import spanner_database_admin -from google.cloud.spanner_admin_instance_v1.types import spanner_instance_admin OPERATION_TIMEOUT_SECONDS = 240 @@ -35,6 +29,8 @@ # [START spanner_create_instance] def create_instance(instance_id): """Creates an instance.""" + from google.cloud.spanner_admin_instance_v1.types import spanner_instance_admin + spanner_client = spanner.Client() config_name = "{}/instanceConfigs/regional-us-central1".format( @@ -68,6 +64,8 @@ def create_instance(instance_id): # [START spanner_create_instance_with_processing_units] def create_instance_with_processing_units(instance_id, processing_units): """Creates an instance.""" + from google.cloud.spanner_admin_instance_v1.types import spanner_instance_admin + spanner_client = spanner.Client() config_name = "{}/instanceConfigs/regional-us-central1".format( @@ -107,6 +105,8 @@ def create_instance_with_processing_units(instance_id, processing_units): # [START spanner_create_database] def create_database(instance_id, database_id): """Creates a database and tables for sample data.""" + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) @@ -140,12 +140,42 @@ def create_database(instance_id, database_id): print("Created database {} on instance {}".format(database.name, instance.name)) +# [START spanner_update_database] +def update_database(instance_id, database_id): + """Updates the drop protection setting for a database.""" + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + + request = spanner_database_admin.UpdateDatabaseRequest( + database=spanner_database_admin.Database( + name="{}/databases/{}".format(instance.name, database_id), + enable_drop_protection=True, + ), + update_mask={"paths": ["enable_drop_protection"]}, + ) + operation = spanner_client.database_admin_api.update_database(request=request) + print( + "Waiting for update operation for {}/databases/{} to complete...".format( + instance.name, database_id + ) + ) + operation.result(OPERATION_TIMEOUT_SECONDS) + + print("Updated database {}/databases/{}.".format(instance.name, database_id)) + + +# [END spanner_update_database] + # [END spanner_create_database] # [START spanner_create_database_with_default_leader] def create_database_with_default_leader(instance_id, database_id, default_leader): """Creates a database with tables with a default leader.""" + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) @@ -187,9 +217,40 @@ def create_database_with_default_leader(instance_id, database_id, default_leader # [END spanner_create_database_with_default_leader] +# [START spanner_update_database_with_default_leader] +def update_database_with_default_leader(instance_id, database_id, default_leader): + """Updates a database with tables with a default leader.""" + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + + spanner_client = spanner.Client() + instance = spanner_client.instance(instance_id) + database = instance.database(database_id) + + request = spanner_database_admin.UpdateDatabaseDdlRequest( + database=database.name, + statements=[ + "ALTER DATABASE {}" + " SET OPTIONS (default_leader = '{}')".format(database_id, default_leader) + ], + ) + operation = spanner_client.database_admin_api.update_database_ddl(request) + + database = operation.result(OPERATION_TIMEOUT_SECONDS) + + print( + "Database {} updated with default leader {}".format( + database.name, database.default_leader + ) + ) + + +# [END spanner_update_database_with_default_leader] + + # [START spanner_create_database_with_encryption_key] def create_database_with_encryption_key(instance_id, database_id, kms_key_name): """Creates a database with tables using a Customer Managed Encryption Key (CMEK).""" + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin from google.cloud.spanner_admin_database_v1 import EncryptionConfig spanner_client = spanner.Client() @@ -235,6 +296,9 @@ def add_and_drop_database_roles(instance_id, database_id): # [START spanner_add_and_drop_database_role] # instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -278,6 +342,9 @@ def create_table_with_datatypes(instance_id, database_id): # [START spanner_create_table_with_datatypes] # instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -318,6 +385,8 @@ def add_json_column(instance_id, database_id): # instance_id = "your-spanner-instance" # database_id = "your-spanner-db-id" + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -345,6 +414,9 @@ def add_json_column(instance_id, database_id): # [START spanner_add_numeric_column] def add_numeric_column(instance_id, database_id): """Adds a new NUMERIC column to the Venues table in the example database.""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -373,6 +445,8 @@ def add_numeric_column(instance_id, database_id): def create_table_with_timestamp(instance_id, database_id): """Creates a table with a COMMIT_TIMESTAMP column.""" + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -410,6 +484,9 @@ def create_table_with_timestamp(instance_id, database_id): # [START spanner_create_table_with_foreign_key_delete_cascade] def create_table_with_foreign_key_delete_cascade(instance_id, database_id): """Creates a table with foreign key delete cascade action""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -453,6 +530,9 @@ def create_table_with_foreign_key_delete_cascade(instance_id, database_id): # [START spanner_alter_table_with_foreign_key_delete_cascade] def alter_table_with_foreign_key_delete_cascade(instance_id, database_id): """Alters a table with foreign key delete cascade action""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -487,6 +567,9 @@ def alter_table_with_foreign_key_delete_cascade(instance_id, database_id): # [START spanner_drop_foreign_key_constraint_delete_cascade] def drop_foreign_key_constraint_delete_cascade(instance_id, database_id): """Alter table to drop foreign key delete cascade action""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -518,6 +601,9 @@ def drop_foreign_key_constraint_delete_cascade(instance_id, database_id): # [START spanner_create_sequence] def create_sequence(instance_id, database_id): """Creates the Sequence and insert data""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -569,6 +655,9 @@ def insert_customers(transaction): # [START spanner_alter_sequence] def alter_sequence(instance_id, database_id): """Alters the Sequence and insert data""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -616,6 +705,9 @@ def insert_customers(transaction): # [START spanner_drop_sequence] def drop_sequence(instance_id, database_id): """Drops the Sequence""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -646,6 +738,9 @@ def drop_sequence(instance_id, database_id): # [START spanner_add_column] def add_column(instance_id, database_id): """Adds a new column to the Albums table in the example database.""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -670,6 +765,9 @@ def add_column(instance_id, database_id): # [START spanner_add_timestamp_column] def add_timestamp_column(instance_id, database_id): """Adds a new TIMESTAMP column to the Albums table in the example database.""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) @@ -701,6 +799,9 @@ def add_timestamp_column(instance_id, database_id): # [START spanner_create_index] def add_index(instance_id, database_id): """Adds a simple index to the example database.""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -724,6 +825,9 @@ def add_index(instance_id, database_id): # [START spanner_create_storing_index] def add_storing_index(instance_id, database_id): """Adds an storing index to the example database.""" + + from google.cloud.spanner_admin_database_v1.types import spanner_database_admin + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) @@ -761,6 +865,12 @@ def enable_fine_grained_access( # iam_member = "user:alice@example.com" # database_role = "new_parent" # title = "condition title" + + from google.type import expr_pb2 + from google.iam.v1 import iam_policy_pb2 + from google.iam.v1 import options_pb2 + from google.iam.v1 import policy_pb2 + spanner_client = spanner.Client() instance = spanner_client.instance(instance_id) database = instance.database(database_id) diff --git a/samples/samples/admin/samples_test.py b/samples/samples/admin/samples_test.py index c341b6c85b..959c2f48fc 100644 --- a/samples/samples/admin/samples_test.py +++ b/samples/samples/admin/samples_test.py @@ -161,6 +161,7 @@ def test_create_database_with_encryption_config( assert kms_key_name in out +@pytest.mark.dependency(name="create_database_with_default_leader") def test_create_database_with_default_leader( capsys, multi_region_instance, @@ -177,6 +178,34 @@ def test_create_database_with_default_leader( assert default_leader in out +@pytest.mark.dependency(depends=["create_database_with_default_leader"]) +def test_update_database_with_default_leader( + capsys, + multi_region_instance, + multi_region_instance_id, + default_leader_database_id, + default_leader, +): + retry_429 = RetryErrors(exceptions.ResourceExhausted, delay=15) + retry_429(samples.update_database_with_default_leader)( + multi_region_instance_id, default_leader_database_id, default_leader + ) + out, _ = capsys.readouterr() + assert default_leader_database_id in out + assert default_leader in out + + +def test_update_database(capsys, instance_id, sample_database): + samples.update_database(instance_id, sample_database.database_id) + out, _ = capsys.readouterr() + assert "Updated database {}.".format(sample_database.name) in out + + # Cleanup + sample_database.enable_drop_protection = False + op = sample_database.update(["enable_drop_protection"]) + op.result() + + @pytest.mark.dependency( name="add_and_drop_database_roles", depends=["create_table_with_datatypes"] )