From e264a942b659d27571e3387f4e9ddae5c47b25e8 Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Wed, 25 Nov 2020 18:32:05 +0300 Subject: [PATCH 01/31] docs for base --- django_spanner/base.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/django_spanner/base.py b/django_spanner/base.py index 044f8ddd75..5f75fda3f9 100644 --- a/django_spanner/base.py +++ b/django_spanner/base.py @@ -103,6 +103,11 @@ class DatabaseWrapper(BaseDatabaseWrapper): @property def instance(self): + """Instance to which this connection relates. + + :rtype: :class:`~google.cloud.spanner_v1.instance.Instance` + :returns: The related instance object. + """ return spanner.Client().instance(self.settings_dict["INSTANCE"]) @property @@ -112,6 +117,11 @@ def _nodb_connection(self): ) def get_connection_params(self): + """Dictionary of the connection parameters. + + :rtype: dict + :returns: Connection parameters in Django Spanner format. + """ return { "project": self.settings_dict["PROJECT"], "instance_id": self.settings_dict["INSTANCE"], @@ -121,12 +131,30 @@ def get_connection_params(self): } def get_new_connection(self, conn_params): + """Creates a new connection with corresponding connection parameters. + + :type conn_params: list + :param conn_params: A List of the connection parameters for + :class:`~google.cloud.spanner_dbapi.connection.Connection` + + :rtype: :class:`google.cloud.spanner_dbapi.connection.Connection` + :returns: Connection object associated with the given Google Cloud Spanner + resource. + + :raises: :class:`ValueError` in case of given instance/database + doesn't exist. + """ return Database.connect(**conn_params) def init_connection_state(self): pass def create_cursor(self, name=None): + """Factory to create a Cursor. + + :rtype: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` + :returns: The Cursor for this connection. + """ return self.connection.cursor() def _set_autocommit(self, autocommit): @@ -134,6 +162,11 @@ def _set_autocommit(self, autocommit): self.connection.autocommit = autocommit def is_usable(self): + """Checks database to be usable. + + :rtype: bool + :returns: True if database is usable, False otherwise. + """ if self.connection is None: return False try: From 506913283ba4d7930be31ed245a6bf5cc8f840ce Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Wed, 25 Nov 2020 18:52:30 +0300 Subject: [PATCH 02/31] docs for client --- django_spanner/client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django_spanner/client.py b/django_spanner/client.py index 3623632b03..d440819b6d 100644 --- a/django_spanner/client.py +++ b/django_spanner/client.py @@ -9,5 +9,11 @@ class DatabaseClient(BaseDatabaseClient): + """Wraps the Django base class via implementing the `runshell` method. + + TODO: Missing actual implementation of `runshell`. + + :raises: :class:`~google.cloud.spanner_dbapi.exceptions.NotSupportedError` + """ def runshell(self, parameters): raise NotSupportedError("This method is not supported.") From ca9c7ce6b7175ad6fa74ae427e330e99aebeda4e Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 26 Nov 2020 12:27:03 +0300 Subject: [PATCH 03/31] docs for compiler --- django_spanner/compiler.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django_spanner/compiler.py b/django_spanner/compiler.py index 106686d445..46bcb74749 100644 --- a/django_spanner/compiler.py +++ b/django_spanner/compiler.py @@ -21,6 +21,12 @@ def get_combinator_sql(self, combinator, all): Copied from the base class except for: combinator_sql += ' ALL' if all else ' DISTINCT' Cloud Spanner requires ALL or DISTINCT. + + :type combinator: str + :param combinator: A type of the combinator for the operation. + + :type all: bool + :param all: Bool option for the SQL statement. """ features = self.connection.features compilers = [ From 1a6257ab2cb434e0b15aab33a9309d9d383924c3 Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 26 Nov 2020 12:35:08 +0300 Subject: [PATCH 04/31] docs for expressions --- django_spanner/expressions.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/django_spanner/expressions.py b/django_spanner/expressions.py index bb0992c0af..a6e6894592 100644 --- a/django_spanner/expressions.py +++ b/django_spanner/expressions.py @@ -8,6 +8,13 @@ def order_by(self, compiler, connection, **extra_context): + """Order expressions in the SQL query. + + TODO: Make this function a part of the class. + + :rtype: str + :returns: A SQL query. + """ # In Django 3.1, this can be replaced with # DatabaseFeatures.supports_order_by_nulls_modifier = False. template = None @@ -21,4 +28,5 @@ def order_by(self, compiler, connection, **extra_context): def register_expressions(): + """Register ordered expressions in Spanner.""" OrderBy.as_spanner = order_by From cfdba5f90707ede39de13f3e5bfe69e54a5c5e5d Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 26 Nov 2020 12:36:42 +0300 Subject: [PATCH 05/31] docs for features --- django_spanner/features.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django_spanner/features.py b/django_spanner/features.py index 771dd0b939..773b7d5251 100644 --- a/django_spanner/features.py +++ b/django_spanner/features.py @@ -11,6 +11,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): + """This class describes Django database features.""" can_introspect_big_integer_field = False can_introspect_duration_field = False can_introspect_foreign_keys = False From 2b742f82a62c9436a3fde07ce5f95e9a02f9693d Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 26 Nov 2020 13:56:19 +0300 Subject: [PATCH 06/31] docs for functions --- django_spanner/functions.py | 85 +++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/django_spanner/functions.py b/django_spanner/functions.py index e21258a3ec..3fabdab162 100644 --- a/django_spanner/functions.py +++ b/django_spanner/functions.py @@ -30,6 +30,13 @@ class IfNull(Func): def cast(self, compiler, connection, **extra_context): + """Cast SQL query for given parameters. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ # Account for a field's max_length using SUBSTR. max_length = getattr(self.output_field, "max_length", None) if max_length is not None: @@ -42,6 +49,13 @@ def cast(self, compiler, connection, **extra_context): def chr_(self, compiler, connection, **extra_context): + """Return a SQL query where the code points are displayed as a string. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.as_sql( compiler, connection, @@ -51,6 +65,13 @@ def chr_(self, compiler, connection, **extra_context): def concatpair(self, compiler, connection, **extra_context): + """Concatenates a SQL query into the sequence of :class:`IfNull` objects. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ # Spanner's CONCAT function returns null if any of its arguments are null. # Prevent that by converting null arguments to an empty string. clone = self.copy() @@ -61,6 +82,13 @@ def concatpair(self, compiler, connection, **extra_context): def cot(self, compiler, connection, **extra_context): + """Return a SQL query of calculated cotangent. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.as_sql( compiler, connection, @@ -70,6 +98,13 @@ def cot(self, compiler, connection, **extra_context): def degrees(self, compiler, connection, **extra_context): + """Return a SQL query of the angle converted to degrees. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.as_sql( compiler, connection, @@ -79,10 +114,24 @@ def degrees(self, compiler, connection, **extra_context): def left_and_right(self, compiler, connection, **extra_context): + """ + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.get_substr().as_spanner(compiler, connection, **extra_context) def log(self, compiler, connection, **extra_context): + """Return a SQL query of calculated logarithm. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ # This function is usually Log(b, x) returning the logarithm of x to the # base b, but on Spanner it's Log(x, b). clone = self.copy() @@ -91,6 +140,13 @@ def log(self, compiler, connection, **extra_context): def ord_(self, compiler, connection, **extra_context): + """Return a SQL query of the expression converted to ord. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.as_sql( compiler, connection, @@ -100,12 +156,26 @@ def ord_(self, compiler, connection, **extra_context): def pi(self, compiler, connection, **extra_context): + """Return a SQL query of PI constant. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.as_sql( compiler, connection, template=str(math.pi), **extra_context ) def radians(self, compiler, connection, **extra_context): + """Return a SQL query of the angle converted to radians. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.as_sql( compiler, connection, @@ -115,18 +185,33 @@ def radians(self, compiler, connection, **extra_context): def strindex(self, compiler, connection, **extra_context): + """Return a SQL query of the string position. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.as_sql( compiler, connection, function="STRPOS", **extra_context ) def substr(self, compiler, connection, **extra_context): + """Return a SQL query of substring. + + TODO: describe all parameters when code will be ready. + + :rtype: str + :returns: A SQL query. + """ return self.as_sql( compiler, connection, function="SUBSTR", **extra_context ) def register_functions(): + """Register functions in Spanner.""" Cast.as_spanner = cast Chr.as_spanner = chr_ ConcatPair.as_spanner = concatpair From a82bf1b80750ab80e5545da0e016abfce1d12e54 Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 26 Nov 2020 14:10:22 +0300 Subject: [PATCH 07/31] docs for introspection --- django_spanner/introspection.py | 61 ++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/django_spanner/introspection.py b/django_spanner/introspection.py index ab9d29aa3f..34e5fec848 100644 --- a/django_spanner/introspection.py +++ b/django_spanner/introspection.py @@ -14,6 +14,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): + """This class describes database introspection.""" data_types_reverse = { type_pb2.BOOL: "BooleanField", type_pb2.BYTES: "BinaryField", @@ -25,12 +26,30 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): } def get_field_type(self, data_type, description): + """Define the type of the field. + + :type data_type: str + :param data_type: The type name. + + :type description: str + :param description: The description of the type. + + :rtype: Any + :returns: Field type. + """ if data_type == type_pb2.STRING and description.internal_size == "MAX": return "TextField" return super().get_field_type(data_type, description) def get_table_list(self, cursor): - """Return a list of table and view names in the current database.""" + """Return a list of table and view names in the current database. + + :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` + :param cursor: The cursor in the linked database. + + :rtype: list + :returns: A list of table and view names. + """ # The second TableInfo field is 't' for table or 'v' for view. return [TableInfo(row[0], "t") for row in cursor.list_tables()] @@ -38,6 +57,15 @@ def get_table_description(self, cursor, table_name): """ Return a description of the table with the DB-API cursor.description interface. + + :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` + :param cursor: The cursor in the linked database. + + :type table_name: str + :param table_name: The name of the table. + + :rtype: list + :returns: A description of the table. """ cursor.execute( "SELECT * FROM %s LIMIT 1" @@ -78,6 +106,15 @@ def get_relations(self, cursor, table_name): """ Return a dictionary of {field_name: (field_name_other_table, other_table)} representing all relationships in the table. + + :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` + :param cursor: The cursor in the linked database. + + :type table_name: str + :param table_name: The name of the table. + + :rtype: dict + :returns: A dictionary of the field descriptions. """ results = cursor.run_sql_in_snapshot( ''' @@ -103,6 +140,17 @@ def get_relations(self, cursor, table_name): } def get_primary_key_column(self, cursor, table_name): + """Return Primary Key column. + + :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` + :param cursor: The cursor in the linked database. + + :type table_name: str + :param table_name: The name of the table. + + :rtype: str + :returns: The name of the PK column. + """ results = cursor.run_sql_in_snapshot( """ SELECT @@ -121,6 +169,17 @@ def get_primary_key_column(self, cursor, table_name): return results[0][0] if results else None def get_constraints(self, cursor, table_name): + """Return a dictionary the constraints in the current table. + + :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` + :param cursor: The cursor in the linked database. + + :type table_name: str + :param table_name: The name of the table. + + :rtype: dict + :returns: A dictionary with constraints. + """ constraints = {} quoted_table_name = self.connection.ops.quote_name(table_name) From 321858fa7fdd722c1dcfec2ec55a397b26f70bf6 Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 26 Nov 2020 16:56:42 +0300 Subject: [PATCH 08/31] docs for lookups --- django_spanner/lookups.py | 41 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/django_spanner/lookups.py b/django_spanner/lookups.py index 726a8f8611..56e415eb0d 100644 --- a/django_spanner/lookups.py +++ b/django_spanner/lookups.py @@ -24,7 +24,13 @@ def contains(self, compiler, connection): - """contains and icontains""" + """Contains and icontains. + + TODO: describe all parameters when code will be ready. + + :rtype: tuple[str, str] + :returns: A tuple of the SQL request and parameters. + """ lhs_sql, params = self.process_lhs(compiler, connection) rhs_sql, rhs_params = self.process_rhs(compiler, connection) params.extend(rhs_params) @@ -52,6 +58,15 @@ def contains(self, compiler, connection): def iexact(self, compiler, connection): + """ + Case-insensitive exact match. If the value provided for comparison is + None, it will be interpreted as an SQL NULL. + + TODO: describe all parameters when code will be ready. + + :rtype: tuple[str, str] + :returns: A tuple of the SQL request and parameters. + """ lhs_sql, params = self.process_lhs(compiler, connection) rhs_sql, rhs_params = self.process_rhs(compiler, connection) params.extend(rhs_params) @@ -69,7 +84,13 @@ def iexact(self, compiler, connection): def regex(self, compiler, connection): - """regex and iregex""" + """Regex and iregex. + + TODO: describe all parameters when code will be ready. + + :rtype: tuple[str, str] + :returns: A tuple of the SQL request and parameters. + """ lhs_sql, params = self.process_lhs(compiler, connection) rhs_sql, rhs_params = self.process_rhs(compiler, connection) params.extend(rhs_params) @@ -91,7 +112,13 @@ def regex(self, compiler, connection): def startswith_endswith(self, compiler, connection): - """startswith, endswith, istartswith, and iendswith lookups.""" + """Startswith, endswith, istartswith, and iendswith lookups. + + TODO: describe all parameters when code will be ready. + + :rtype: tuple[str, str] + :returns: A tuple of the SQL request and parameters. + """ lhs_sql, params = self.process_lhs(compiler, connection) rhs_sql, rhs_params = self.process_rhs(compiler, connection) params.extend(rhs_params) @@ -131,6 +158,13 @@ def startswith_endswith(self, compiler, connection): def cast_param_to_float(self, compiler, connection): + """Cast parameters of the expression into the float type. + + TODO: describe all parameters when code will be ready. + + :rtype: tuple[str, str] + :returns: A tuple of the SQL request and float parameters. + """ sql, params = self.as_sql(compiler, connection) if params: # Cast to DecimaField lookup values to float because @@ -151,6 +185,7 @@ def cast_param_to_float(self, compiler, connection): def register_lookups(): + """Register a new lookup in the class.""" Contains.as_spanner = contains IContains.as_spanner = contains IExact.as_spanner = iexact From e77b4e963077730c322e467fb84e5502cb549046 Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 3 Dec 2020 17:52:59 +0300 Subject: [PATCH 09/31] docs for operations --- django_spanner/operations.py | 305 ++++++++++++++++++++++++++++++++++- 1 file changed, 303 insertions(+), 2 deletions(-) diff --git a/django_spanner/operations.py b/django_spanner/operations.py index be8cd04e67..906e23841d 100644 --- a/django_spanner/operations.py +++ b/django_spanner/operations.py @@ -24,6 +24,7 @@ class DatabaseOperations(BaseDatabaseOperations): + """This class describes database operations.""" cast_data_types = {"CharField": "STRING", "TextField": "STRING"} cast_char_field_without_max_length = "STRING" compiler_module = "django_spanner.compiler" @@ -37,10 +38,23 @@ class DatabaseOperations(BaseDatabaseOperations): } def max_name_length(self): + """The quota for the maximum table name length. + + :rtype: int + :returns: Maximum length of the name of the table. + """ # https://cloud.google.com/spanner/quotas#tables return 128 def quote_name(self, name): + """Change quote name to the necessary format. + + :type name: str + :param name: The Quota name. + + :rtype: :class:`str` + :returns: Name escaped if it has to be escaped. + """ # Spanner says "column name not valid" if spaces or hyphens are present # (although according the docs, any character should be allowed in # quoted identifiers). Replace problematic characters when running the @@ -52,14 +66,47 @@ def quote_name(self, name): return escape_name(name) def bulk_batch_size(self, fields, objs): + """Return the maximum number of the query parameters. + + TODO: describe the rest of the parameters or remove them. + + :rtype: int + :returns: The maximum number of the query parameters (constant). + """ return self.connection.features.max_query_params def bulk_insert_sql(self, fields, placeholder_rows): + """Bulk insert for SQLs. + + TODO: describe the rest of the parameters or remove them. + + :type placeholder_rows: list + :param placeholder_rows: A list of placeholder rows. + + :rtype: str + :returns: A SQL statement. + """ placeholder_rows_sql = (", ".join(row) for row in placeholder_rows) values_sql = ", ".join("(%s)" % sql for sql in placeholder_rows_sql) return "VALUES " + values_sql def sql_flush(self, style, tables, sequences, allow_cascade=False): + """ + Return a list of SQL statements required to remove all data from the + given database tables (without actually removing the tables themselves). + + :type style: :class:`~django.core.management.color.Style` + :param style: An object as returned by either color_style() or + no_style(). + + :type tables: list + :param tables: A list of tables names. + + TODO: describe the rest of the parameters or remove them. + + :rtype: list + :returns: A list of SQL requests to tables. + """ # Cloud Spanner doesn't support TRUNCATE so DELETE instead. # A dummy WHERE clause is required. if tables: @@ -75,11 +122,27 @@ def sql_flush(self, style, tables, sequences, allow_cascade=False): return [] def adapt_datefield_value(self, value): + """Reformat date argument into Cloud Spanner. + + :type value: #TODO + :param value: A date argument. + + :rtype: :class:`~google.cloud.spanner_dbapi.types.DateStr` + :returns: Formatted Date. + """ if value is None: return None return DateStr(str(value)) def adapt_datetimefield_value(self, value): + """Reformat time argument into Cloud Spanner. + + :type value: #TODO + :param value: A time argument. + + :rtype: :class:`~google.cloud.spanner_dbapi.types.TimestampStr` + :returns: Formatted Time. + """ if value is None: return None # Expression values are adapted by the database. @@ -102,12 +165,35 @@ def adapt_decimalfield_value( """ Convert value from decimal.Decimal into float, for a direct mapping and correct serialization with RPCs to Cloud Spanner. + + :type value: #TODO + :param value: A decimal field value. + + :type max_digits: int + :param max_digits: (Optional) A maximum number of digits. + + :type decimal_places: int + :param decimal_places: (Optional) The number of decimal places to store + with the number. + + :rtype: float + :returns: Formatted value. """ if value is None: return None return float(value) def adapt_timefield_value(self, value): + """ + Transforms a time value to an object compatible with what is expected + by the backend driver for time columns. + + :type value: #TODO + :param value: A time field value. + + :rtype: :class:`~google.cloud.spanner_dbapi.types.TimestampStr` + :returns: Formatted Time. + """ if value is None: return None # Expression values are adapted by the database. @@ -119,6 +205,14 @@ def adapt_timefield_value(self, value): ) def get_db_converters(self, expression): + """Get a list of functions needed to convert field data. + + :type expression: #TODO finish class in functions.py + :param expression: An expression to convert. + + :rtype: list + :returns: A list of functions. + """ converters = super().get_db_converters(expression) internal_type = expression.output_field.get_internal_type() if internal_type == "DateTimeField": @@ -134,12 +228,32 @@ def get_db_converters(self, expression): return converters def convert_binaryfield_value(self, value, expression, connection): + """Convert a binary field to Cloud Spanner. + + :type value: #TODO + :param value: A binary field value. + + #TODO remove unused expression and connection parameters or include them to the code. + + :rtype: b64decode + :returns: A base64 encoded bytes. + """ if value is None: return value # Cloud Spanner stores bytes base64 encoded. return b64decode(value) def convert_datetimefield_value(self, value, expression, connection): + """Convert a date and time field to Cloud Spanner. + + :type value: #TODO + :param value: A binary field value. + + #TODO remove unused expression and connection parameters or include them to the code. + + :rtype: datetime + :returns: A DateTime in the format for Cloud Spanner. + """ if value is None: return value # Cloud Spanner returns the @@ -163,27 +277,83 @@ def convert_datetimefield_value(self, value, expression, connection): ) def convert_decimalfield_value(self, value, expression, connection): + """Convert a decimal field to Cloud Spanner. + + :type value: #TODO + :param value: A decimal field. + + #TODO remove unused expression and connection parameters or include them to the code. + + :rtype: :class:`Decimal` + :returns: A decimal field in the Cloud Spanner format. + """ if value is None: return value # Cloud Spanner returns a float. return Decimal(str(value)) def convert_timefield_value(self, value, expression, connection): + """Convert a time field to Cloud Spanner. + + :type value: #TODO + :param value: A time field. + + #TODO remove unused expression and connection parameters or include them to the code. + + :rtype: :class:`time` + :returns: A time field in the Cloud Spanner format. + """ if value is None: return value # Convert DatetimeWithNanoseconds to time. return time(value.hour, value.minute, value.second, value.microsecond) def convert_uuidfield_value(self, value, expression, connection): + """Convert a UUID field to Cloud Spanner. + + :type value: #TODO + :param value: A UUID field. + + #TODO remove unused expression and connection parameters or include them to the code. + + :rtype: :class:`UUID` + :returns: A UUID field in the Cloud Spanner format. + """ if value is not None: value = UUID(value) return value def date_extract_sql(self, lookup_type, field_name): + """Extract date from the lookup. + + :type lookup_type: str + :param lookup_type: A type of the lookup. + + :type field_name: str + :param field_name: The name of the field. + + :rtype: str + :returns: A SQL statement for extracting. + """ lookup_type = self.extract_names.get(lookup_type, lookup_type) return "EXTRACT(%s FROM %s)" % (lookup_type, field_name) def datetime_extract_sql(self, lookup_type, field_name, tzname): + """Extract datetime from the lookup. + + :type lookup_type: str + :param lookup_type: A type of the lookup. + + :type field_name: str + :param field_name: The name of the field. + + :type tzname: str + :param tzname: The time zone name. If using of time zone is not + allowed in settings default will be UTC. + + :rtype: str + :returns: A SQL statement for extracting. + """ tzname = tzname if settings.USE_TZ else "UTC" lookup_type = self.extract_names.get(lookup_type, lookup_type) return 'EXTRACT(%s FROM %s AT TIME ZONE "%s")' % ( @@ -193,6 +363,17 @@ def datetime_extract_sql(self, lookup_type, field_name, tzname): ) def time_extract_sql(self, lookup_type, field_name): + """Extract time from the lookup. + + :type lookup_type: str + :param lookup_type: A type of the lookup. + + :type field_name: str + :param field_name: The name of the field. + + :rtype: str + :returns: A SQL statement for extracting. + """ # Time is stored as TIMESTAMP with UTC time zone. return 'EXTRACT(%s FROM %s AT TIME ZONE "UTC")' % ( lookup_type, @@ -200,6 +381,17 @@ def time_extract_sql(self, lookup_type, field_name): ) def date_trunc_sql(self, lookup_type, field_name): + """Truncate date in the lookup. + + :type lookup_type: str + :param lookup_type: A type of the lookup. + + :type field_name: str + :param field_name: The name of the field. + + :rtype: str + :returns: A SQL statement for truncating. + """ # https://cloud.google.com/spanner/docs/functions-and-operators#date_trunc if lookup_type == "week": # Spanner truncates to Sunday but Django expects Monday. First, @@ -215,6 +407,17 @@ def date_trunc_sql(self, lookup_type, field_name): return sql def datetime_trunc_sql(self, lookup_type, field_name, tzname): + """Truncate datetime in the lookup. + + :type lookup_type: str + :param lookup_type: A type of the lookup. + + :type field_name: str + :param field_name: The name of the field. + + :rtype: str + :returns: A SQL statement for truncating. + """ # https://cloud.google.com/spanner/docs/functions-and-operators#timestamp_trunc tzname = tzname if settings.USE_TZ else "UTC" if lookup_type == "week": @@ -233,15 +436,50 @@ def datetime_trunc_sql(self, lookup_type, field_name, tzname): return sql def time_trunc_sql(self, lookup_type, field_name): + """Truncate time in the lookup. + + :type lookup_type: str + :param lookup_type: A type of the lookup. + + :type field_name: str + :param field_name: The name of the field. + + :rtype: str + :returns: A SQL statement for truncating. + """ # https://cloud.google.com/spanner/docs/functions-and-operators#timestamp_trunc return 'TIMESTAMP_TRUNC(%s, %s, "UTC")' % (field_name, lookup_type) def datetime_cast_date_sql(self, field_name, tzname): + """Cast date in the lookup. + + :type field_name: str + :param field_name: The name of the field. + + :type tzname: str + :param tzname: The time zone name. If using of time zone is not + allowed in settings default will be UTC. + + :rtype: str + :returns: A SQL statement for casting. + """ # https://cloud.google.com/spanner/docs/functions-and-operators#date tzname = tzname if settings.USE_TZ else "UTC" return 'DATE(%s, "%s")' % (field_name, tzname) def datetime_cast_time_sql(self, field_name, tzname): + """Cast time in the lookup. + + :type field_name: str + :param field_name: The name of the field. + + :type tzname: str + :param tzname: The time zone name. If using of time zone is not + allowed in settings default will be UTC. + + :rtype: str + :returns: A SQL statement for casting. + """ tzname = tzname if settings.USE_TZ else "UTC" # Cloud Spanner doesn't have a function for converting # TIMESTAMP to another time zone. @@ -251,12 +489,39 @@ def datetime_cast_time_sql(self, field_name, tzname): ) def date_interval_sql(self, timedelta): + """Get a date interval in microseconds. + + :type timedelta: datetime + :param timedelta: A time delta for the interval. + + :rtype: str + :returns: A SQL statement. + """ return "INTERVAL %s MICROSECOND" % duration_microseconds(timedelta) def format_for_duration_arithmetic(self, sql): + """Do nothing since formatting is handled in the custom function. + + :type sql: str + :param sql: A SQL statement. + + :rtype: str + :return: A SQL statement. + """ return "INTERVAL %s MICROSECOND" % sql def combine_expression(self, connector, sub_expressions): + """Recurrently combine expressions into single one using connector. + + :type connector: str + :param connector: A type of connector operation. + + :type sub_expressions: list + :param sub_expressions: A list of expressions to combine. + + :rtype: str + :return: A SQL statement for combining. + """ if connector == "%%": return "MOD(%s)" % ", ".join(sub_expressions) elif connector == "^": @@ -276,6 +541,19 @@ def combine_expression(self, connector, sub_expressions): return super().combine_expression(connector, sub_expressions) def combine_duration_expression(self, connector, sub_expressions): + """Combine duration expressions into single one using connector. + + :type connector: str + :param connector: A type of connector operation. + + :type sub_expressions: list + :param sub_expressions: A list of expressions to combine. + + :raises: :class:`~django.db.utils.DatabaseError` + + :rtype: str + :return: A SQL statement for combining. + """ if connector == "+": return "TIMESTAMP_ADD(" + ", ".join(sub_expressions) + ")" elif connector == "-": @@ -286,6 +564,18 @@ def combine_duration_expression(self, connector, sub_expressions): ) def lookup_cast(self, lookup_type, internal_type=None): + """ + Cast text lookups to string to allow things like filter(x__contains=4). + + :type lookup_type: str + :param lookup_type: A type of the lookup. + + :type internal_type: str + :param internal_type: (Optional) + + :rtype: str + :return: A SQL statement. + """ # Cast text lookups to string to allow things like # filter(x__contains=4) if lookup_type in ( @@ -303,13 +593,24 @@ def lookup_cast(self, lookup_type, internal_type=None): return "%s" def prep_for_like_query(self, x): - """Lookups that use this method use REGEXP_CONTAINS instead of LIKE.""" + """Lookups that use this method use REGEXP_CONTAINS instead of LIKE. + + :type x: str + :param x: A query to prepare. + + :rtype: str + :returns: A prepared query. + """ return re.escape(str(x)) prep_for_iexact_query = prep_for_like_query def no_limit_value(self): - """The largest INT64: (2**63) - 1""" + """The largest INT64: (2**63) - 1 + + :rtype: int + :returns: The largest INT64. + """ return 9223372036854775807 def _get_limit_offset_params(self, low_mark, high_mark): From 4db2d4a99bd5fe5fd21d471105ba41d93d401339 Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 3 Dec 2020 19:04:59 +0300 Subject: [PATCH 10/31] docs for schema --- django_spanner/schema.py | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/django_spanner/schema.py b/django_spanner/schema.py index c3cafcd0eb..11dd92079c 100644 --- a/django_spanner/schema.py +++ b/django_spanner/schema.py @@ -9,6 +9,10 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): + """ + The database abstraction layer that turns things like “create a model” or + “delete a field” into SQL. + """ sql_create_table = ( "CREATE TABLE %(table)s (%(definition)s) PRIMARY KEY(%(primary_key)s)" ) @@ -38,6 +42,9 @@ def create_model(self, model): """ Create a table and any accompanying indexes or unique constraints for the given `model`. + + :type model: :class:`~django.db.migrations.operations.models.ModelOperation` + :param model: A model for creating a table. """ # Create column SQL, add FK deferreds if needed column_sqls = [] @@ -123,6 +130,13 @@ def create_model(self, model): self.create_model(field.remote_field.through) def delete_model(self, model): + """ + Drop the model’s table in the database along with any unique constraints + or indexes it has. + + :type model: :class:`~django.db.migrations.operations.models.ModelOperation` + :param model: A model for creating a table. + """ # Spanner requires dropping all of a table's indexes before dropping # the table. index_names = self._constraint_names( @@ -133,6 +147,21 @@ def delete_model(self, model): super().delete_model(model) def add_field(self, model, field): + """ + Add a column (or sometimes multiple) to the model’s table to + represent the field. This will also add indexes or a unique constraint + if the field has db_index=True or unique=True. If the field is a + ManyToManyField without a value for through, instead of creating a + column, it will make a table to represent the relationship. If through + is provided, it is a no-op. If the field is a ForeignKey, this will + also add the foreign key constraint to the column. + + :type model: :class:`~django.db.migrations.operations.models.ModelOperation` + :param model: A model for creating a table. + + :type field: :class:`~django.db.migrations.operations.models.fields.FieldOperation` + :param field: The field of the table. + """ # Special-case implicit M2M tables if ( field.many_to_many @@ -203,6 +232,19 @@ def add_field(self, model, field): ) def remove_field(self, model, field): + """ + Remove the column(s) representing the field from the model’s table, + along with any unique constraints, foreign key constraints, or indexes + caused by that field. If the field is a ManyToManyField without a + value for through, it will remove the table created to track the + relationship. If through is provided, it is a no-op. + + :type model: :class:`~django.db.migrations.operations.models.ModelOperation` + :param model: A model for creating a table. + + :type field: :class:`~django.db.migrations.operations.models.fields.FieldOperation` + :param field: The field of the table. + """ # Spanner requires dropping a column's indexes before dropping the # column. index_names = self._constraint_names(model, [field.column], index=True) @@ -216,6 +258,18 @@ def column_sql( """ Take a field and return its column definition. The field must already have had set_attributes_from_name() called. + + :type model: :class:`~django.db.migrations.operations.models.ModelOperation` + :param model: A model for creating a table. + + :type field: :class:`~django.db.migrations.operations.models.fields.FieldOperation` + :param field: The field of the table. + + :type include_default: bool + :param include_default: (Optional) Flag for including default fields. + + :type exclude_not_null: bool + :param exclude_not_null: (Optional) Flag for excluding not null fields. """ # Get the column's type and use that as the basis of the SQL db_params = field.db_parameters(connection=self.connection) @@ -250,6 +304,14 @@ def column_sql( return sql, params def add_index(self, model, index): + """Add index to model’s table. + + :type model: :class:`~django.db.migrations.operations.models.ModelOperation` + :param model: A model for creating a table. + + :type index: :class:`~django.db.migrations.operations.models.Index` + :param index: An index to add. + """ # Work around a bug in Django where a space isn't inserting before # DESC: https://code.djangoproject.com/ticket/30961 # This method can be removed in Django 3.1. From 115881f4b871c132eb28d824226877dac9b34802 Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 3 Dec 2020 19:15:59 +0300 Subject: [PATCH 11/31] docs for utils --- django_spanner/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/django_spanner/utils.py b/django_spanner/utils.py index 444afe053d..6fb40db812 100644 --- a/django_spanner/utils.py +++ b/django_spanner/utils.py @@ -31,6 +31,12 @@ def add_dummy_where(sql): """ Cloud Spanner requires a WHERE clause on UPDATE and DELETE statements. Add a dummy WHERE clause if necessary. + + :type sql: str + :param sql: A SQL statement. + + :rtype: str + :returns: A SQL statement with dummy WHERE clause. """ if any( isinstance(token, sqlparse.sql.Where) From 0647468be45a36d8aad9de529ce8824af9745c51 Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 11:17:54 -0500 Subject: [PATCH 12/31] docs: updates to `base.py` --- django_spanner/base.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/django_spanner/base.py b/django_spanner/base.py index 5f75fda3f9..e7c4c62033 100644 --- a/django_spanner/base.py +++ b/django_spanner/base.py @@ -103,10 +103,10 @@ class DatabaseWrapper(BaseDatabaseWrapper): @property def instance(self): - """Instance to which this connection relates. + """Reference to a Cloud Spanner Instance containing the Database. :rtype: :class:`~google.cloud.spanner_v1.instance.Instance` - :returns: The related instance object. + :returns: A new instance owned by the existing Spanner Client. """ return spanner.Client().instance(self.settings_dict["INSTANCE"]) @@ -117,10 +117,11 @@ def _nodb_connection(self): ) def get_connection_params(self): - """Dictionary of the connection parameters. + """Retrieve the connection parameters. :rtype: dict - :returns: Connection parameters in Django Spanner format. + :returns: A dictionary containing the Spanner connection parametersб + in Django Spanner format. """ return { "project": self.settings_dict["PROJECT"], @@ -130,7 +131,7 @@ def get_connection_params(self): **self.settings_dict["OPTIONS"], } - def get_new_connection(self, conn_params): + def get_new_connection(self, **conn_params): """Creates a new connection with corresponding connection parameters. :type conn_params: list @@ -138,19 +139,23 @@ def get_new_connection(self, conn_params): :class:`~google.cloud.spanner_dbapi.connection.Connection` :rtype: :class:`google.cloud.spanner_dbapi.connection.Connection` - :returns: Connection object associated with the given Google Cloud Spanner - resource. + :returns: A new Spanner DB API Connection object associated with the + given Google Cloud Spanner resource. - :raises: :class:`ValueError` in case of given instance/database + :raises: :class:`ValueError` in case the given instance/database doesn't exist. """ return Database.connect(**conn_params) def init_connection_state(self): + """Initialize the state of the existing connection.""" pass def create_cursor(self, name=None): - """Factory to create a Cursor. + """Create a new Database cursor. + + :type name: str + :param name: Currently not used. :rtype: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` :returns: The Cursor for this connection. @@ -158,14 +163,19 @@ def create_cursor(self, name=None): return self.connection.cursor() def _set_autocommit(self, autocommit): + """Set the Spanner transaction autocommit flag. + + :type autocommit: bool + :param autocommit: The new value of the autocommit flag. + """ with self.wrap_database_errors: self.connection.autocommit = autocommit def is_usable(self): - """Checks database to be usable. + """Check whether the connection is valid. :rtype: bool - :returns: True if database is usable, False otherwise. + :returns: True if the connection is open, otherwise False. """ if self.connection is None: return False From 6970b978ef5959769085e315eb368ac429df5f24 Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 11:20:23 -0500 Subject: [PATCH 13/31] docs: updates to `client.py` --- django_spanner/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django_spanner/client.py b/django_spanner/client.py index d440819b6d..e1d3de857c 100644 --- a/django_spanner/client.py +++ b/django_spanner/client.py @@ -9,9 +9,9 @@ class DatabaseClient(BaseDatabaseClient): - """Wraps the Django base class via implementing the `runshell` method. + """Wraps the Django base class. - TODO: Missing actual implementation of `runshell`. + TODO: Consider actual implementation of `runshell` method. :raises: :class:`~google.cloud.spanner_dbapi.exceptions.NotSupportedError` """ From 8dab421a7de5ca5bf0756f3e6449559d17fcaf82 Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Thu, 3 Dec 2020 19:21:03 +0300 Subject: [PATCH 14/31] docs for validation --- django_spanner/validation.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/django_spanner/validation.py b/django_spanner/validation.py index f22dbb3338..99f270d3d6 100644 --- a/django_spanner/validation.py +++ b/django_spanner/validation.py @@ -7,6 +7,17 @@ class DatabaseValidation(BaseDatabaseValidation): def check_field_type(self, field, field_type): + """Check field type and collect errors. + + :type field: :class:`~django.db.migrations.operations.models.fields.FieldOperation` + :param field: The field of the table. + + :type field_type: str + :param field_type: The type of the field. + + :rtype: list + :return: A list of errors. + """ errors = [] # Disable the error when running the Django test suite. if os.environ.get( From 04a0cacd5dd7386c1f7fab7d1ac82b95944fd61c Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 11:27:10 -0500 Subject: [PATCH 15/31] docs: updates to `complier.py` --- django_spanner/compiler.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/django_spanner/compiler.py b/django_spanner/compiler.py index e0bab5d494..f08647a528 100644 --- a/django_spanner/compiler.py +++ b/django_spanner/compiler.py @@ -17,8 +17,13 @@ class SQLCompiler(BaseSQLCompiler): + """A variation of the Django SQL compiler, adjusted for Spanner-specific + functionality. + """ + def get_combinator_sql(self, combinator, all): - """ + """Overrides the native Django method. + Copied from the base class except for: combinator_sql += ' ALL' if all else ' DISTINCT' Cloud Spanner requires ALL or DISTINCT. @@ -28,6 +33,10 @@ def get_combinator_sql(self, combinator, all): :type all: bool :param all: Bool option for the SQL statement. + + :rtype: tuple + :returns: A tuple containing SQL statement(s) with some additional + parameters. """ features = self.connection.features compilers = [ @@ -103,16 +112,20 @@ def get_combinator_sql(self, combinator, all): class SQLInsertCompiler(BaseSQLInsertCompiler, SQLCompiler): + """A wrapper class for compatibility with Django specifications.""" pass class SQLDeleteCompiler(BaseSQLDeleteCompiler, SQLCompiler): + """A wrapper class for compatibility with Django specifications.""" pass class SQLUpdateCompiler(BaseSQLUpdateCompiler, SQLCompiler): + """A wrapper class for compatibility with Django specifications.""" pass class SQLAggregateCompiler(BaseSQLAggregateCompiler, SQLCompiler): + """A wrapper class for compatibility with Django specifications.""" pass From 0d2ec73fdf34694a415680324d855ead1001042f Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 11:28:43 -0500 Subject: [PATCH 16/31] docs: updates to `creation.py` --- django_spanner/creation.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/django_spanner/creation.py b/django_spanner/creation.py index 66bd531170..4e02f39cf6 100644 --- a/django_spanner/creation.py +++ b/django_spanner/creation.py @@ -14,6 +14,10 @@ class DatabaseCreation(BaseDatabaseCreation): + """Spanner-specific wrapper for Django class encapsulating methods for + creation and destruction of the underlying test database. + """ + def mark_skips(self): """Skip tests that don't work on Spanner.""" for test_name in self.connection.features.skip_tests: @@ -30,6 +34,11 @@ def mark_skips(self): ) def create_test_db(self, *args, **kwargs): + """Creates a test database. + + :rtype: str + :returns: The name of the newly created test Database. + """ # This environment variable is set by the Travis build script or # by a developer running the tests locally. if os.environ.get("RUNNING_SPANNER_BACKEND_TESTS") == "1": @@ -37,6 +46,10 @@ def create_test_db(self, *args, **kwargs): super().create_test_db(*args, **kwargs) def _create_test_db(self, verbosity, autoclobber, keepdb=False): + """Creates dummy test tables. This method is mostly copied from the + base class but removes usage of _nodb_connection since Spanner doesn't + have or need one. + """ # Mostly copied from the base class but removes usage of # _nodb_connection since Spanner doesn't have or need one. test_database_name = self._get_test_db_name() From 3a6e84a739dfa90b98d7f9795f87b8430ee489ea Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 11:41:42 -0500 Subject: [PATCH 17/31] docs: updates to `expressions.py` --- django_spanner/expressions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/django_spanner/expressions.py b/django_spanner/expressions.py index a6e6894592..f04f3d4eff 100644 --- a/django_spanner/expressions.py +++ b/django_spanner/expressions.py @@ -8,15 +8,16 @@ def order_by(self, compiler, connection, **extra_context): - """Order expressions in the SQL query. + """Order expressions in the SQL query and generate a new query using + Spanner-specific templates. - TODO: Make this function a part of the class. + TODO: In Django 3.1, this can be replaced with + DatabaseFeatures.supports_order_by_nulls_modifier = False. + Also, consider making this function a part of a class. :rtype: str :returns: A SQL query. """ - # In Django 3.1, this can be replaced with - # DatabaseFeatures.supports_order_by_nulls_modifier = False. template = None if self.nulls_last: template = "%(expression)s IS NULL, %(expression)s %(ordering)s" @@ -28,5 +29,5 @@ def order_by(self, compiler, connection, **extra_context): def register_expressions(): - """Register ordered expressions in Spanner.""" + """Add Spanner-specific attribute to the Django OrderBy class.""" OrderBy.as_spanner = order_by From 1479a2f699b0477e9a7626bf2bf4a85a8e370e84 Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 11:42:45 -0500 Subject: [PATCH 18/31] docs: updates to `features.py` --- django_spanner/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_spanner/features.py b/django_spanner/features.py index 773b7d5251..98e3bc3daf 100644 --- a/django_spanner/features.py +++ b/django_spanner/features.py @@ -11,7 +11,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): - """This class describes Django database features.""" + """An extension to Django database feature descriptor.""" can_introspect_big_integer_field = False can_introspect_duration_field = False can_introspect_foreign_keys = False From c902cd2acf28058e9c7196d32ebab0070a702070 Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 13:04:56 -0500 Subject: [PATCH 19/31] docs: updates to `functions.py` --- django_spanner/functions.py | 257 +++++++++++++++++++++++++++++------- 1 file changed, 208 insertions(+), 49 deletions(-) diff --git a/django_spanner/functions.py b/django_spanner/functions.py index 3fabdab162..9379ef6a64 100644 --- a/django_spanner/functions.py +++ b/django_spanner/functions.py @@ -4,6 +4,8 @@ # license that can be found in the LICENSE file or at # https://developers.google.com/open-source/licenses/bsd +"""Various math helper functions.""" + import math from django.db.models.expressions import Func, Value @@ -25,17 +27,31 @@ class IfNull(Func): + """Represents SQL `IFNULL` function.""" function = "IFNULL" arity = 2 def cast(self, compiler, connection, **extra_context): - """Cast SQL query for given parameters. + """A method to extend Django Cast class. Cast SQL query for given + parameters. + + :type self: :class:`~django.db.models.functions.comparison.Cast` + :param self: the instance of the class that owns this method. - TODO: describe all parameters when code will be ready. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - :rtype: str - :returns: A SQL query. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ # Account for a field's max_length using SUBSTR. max_length = getattr(self.output_field, "max_length", None) @@ -49,12 +65,25 @@ def cast(self, compiler, connection, **extra_context): def chr_(self, compiler, connection, **extra_context): - """Return a SQL query where the code points are displayed as a string. + """A method to extend Django Chr class. Returns a SQL query where the code + points are displayed as a string. + + :type self: :class:`~django.db.models.functions.text.Chr` + :param self: the instance of the class that owns this method. - TODO: describe all parameters when code will be ready. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - :rtype: str - :returns: A SQL query. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.as_sql( compiler, @@ -65,12 +94,25 @@ def chr_(self, compiler, connection, **extra_context): def concatpair(self, compiler, connection, **extra_context): - """Concatenates a SQL query into the sequence of :class:`IfNull` objects. + """A method to extend Django ConcatPair class. Concatenates a SQL query + into the sequence of :class:`IfNull` objects. + + :type self: :class:`~django.db.models.functions.text.ConcatPair` + :param self: the instance of the class that owns this method. + + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - TODO: describe all parameters when code will be ready. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. - :rtype: str - :returns: A SQL query. + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ # Spanner's CONCAT function returns null if any of its arguments are null. # Prevent that by converting null arguments to an empty string. @@ -82,12 +124,25 @@ def concatpair(self, compiler, connection, **extra_context): def cot(self, compiler, connection, **extra_context): - """Return a SQL query of calculated cotangent. + """A method to extend Django Cot class. Returns a SQL query of calculated + trigonometric cotangent function. - TODO: describe all parameters when code will be ready. + :type self: :class:`~django.db.models.functions.math.Cot` + :param self: the instance of the class that owns this method. - :rtype: str - :returns: A SQL query. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. + + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.as_sql( compiler, @@ -98,12 +153,25 @@ def cot(self, compiler, connection, **extra_context): def degrees(self, compiler, connection, **extra_context): - """Return a SQL query of the angle converted to degrees. + """A method to extend Django Degress class. Returns a SQL query of the + angle converted to degrees. + + :type self: :class:`~django.db.models.functions.math.Degrees` + :param self: the instance of the class that owns this method. - TODO: describe all parameters when code will be ready. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - :rtype: str - :returns: A SQL query. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.as_sql( compiler, @@ -114,23 +182,49 @@ def degrees(self, compiler, connection, **extra_context): def left_and_right(self, compiler, connection, **extra_context): - """ + """A method to extend Django Left and Right classes. + + :type self: :class:`~django.db.models.functions.text.Left` or + :class:`~django.db.models.functions.text.Right` + :param self: the instance of the class that owns this method. - TODO: describe all parameters when code will be ready. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - :rtype: str - :returns: A SQL query. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.get_substr().as_spanner(compiler, connection, **extra_context) def log(self, compiler, connection, **extra_context): - """Return a SQL query of calculated logarithm. + """A method to extend Django Log class. Returns a SQL query of calculated + logarithm. + + :type self: :class:`~django.db.models.functions.math.Log` + :param self: the instance of the class that owns this method. - TODO: describe all parameters when code will be ready. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - :rtype: str - :returns: A SQL query. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ # This function is usually Log(b, x) returning the logarithm of x to the # base b, but on Spanner it's Log(x, b). @@ -140,12 +234,25 @@ def log(self, compiler, connection, **extra_context): def ord_(self, compiler, connection, **extra_context): - """Return a SQL query of the expression converted to ord. + """A method to extend Django Ord class. Returns a SQL query of the + expression converted to ord. + + :type self: :class:`~django.db.models.functions.text.Ord` + :param self: the instance of the class that owns this method. - TODO: describe all parameters when code will be ready. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - :rtype: str - :returns: A SQL query. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.as_sql( compiler, @@ -156,12 +263,25 @@ def ord_(self, compiler, connection, **extra_context): def pi(self, compiler, connection, **extra_context): - """Return a SQL query of PI constant. + """A method to extend Django Pi class. Returns a SQL query of the Pi + constant. + + :type self: :class:`~django.db.models.functions.math.Pi` + :param self: the instance of the class that owns this method. + + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - TODO: describe all parameters when code will be ready. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. - :rtype: str - :returns: A SQL query. + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.as_sql( compiler, connection, template=str(math.pi), **extra_context @@ -169,12 +289,25 @@ def pi(self, compiler, connection, **extra_context): def radians(self, compiler, connection, **extra_context): - """Return a SQL query of the angle converted to radians. + """A method to extend Django Radians class. Returns a SQL query of the + angle converted to radians. - TODO: describe all parameters when code will be ready. + :type self: :class:`~django.db.models.functions.math.Radians` + :param self: the instance of the class that owns this method. - :rtype: str - :returns: A SQL query. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. + + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.as_sql( compiler, @@ -185,12 +318,25 @@ def radians(self, compiler, connection, **extra_context): def strindex(self, compiler, connection, **extra_context): - """Return a SQL query of the string position. + """A method to extend Django StrIndex class. Returns a SQL query of the + string position. + + :type self: :class:`~django.db.models.functions.text.StrIndex` + :param self: the instance of the class that owns this method. - TODO: describe all parameters when code will be ready. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - :rtype: str - :returns: A SQL query. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. + + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.as_sql( compiler, connection, function="STRPOS", **extra_context @@ -198,12 +344,25 @@ def strindex(self, compiler, connection, **extra_context): def substr(self, compiler, connection, **extra_context): - """Return a SQL query of substring. + """A method to extend Django Substr class. Returns a SQL query of a + substring. + + :type self: :class:`~django.db.models.functions.text.Substr` + :param self: the instance of the class that owns this method. + + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - TODO: describe all parameters when code will be ready. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. - :rtype: str - :returns: A SQL query. + :rtype: tuple(str, list) + :returns: A tuple where `str` is a string containing ordered SQL parameters + to be replaced with the elements of the `list`. """ return self.as_sql( compiler, connection, function="SUBSTR", **extra_context @@ -211,7 +370,7 @@ def substr(self, compiler, connection, **extra_context): def register_functions(): - """Register functions in Spanner.""" + """Registers the above methods with the corersponding Django classes.""" Cast.as_spanner = cast Chr.as_spanner = chr_ ConcatPair.as_spanner = concatpair From 6cfb4a5553b3db9321816315f6f47a61fb86ba05 Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 13:12:34 -0500 Subject: [PATCH 20/31] docs: updates to `introspection.py` --- django_spanner/introspection.py | 53 +++++++++++++++++---------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/django_spanner/introspection.py b/django_spanner/introspection.py index 12a4016f27..2dd7341972 100644 --- a/django_spanner/introspection.py +++ b/django_spanner/introspection.py @@ -14,7 +14,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): - """This class describes database introspection.""" + """A Spanner-specific version of Django introspection utilities.""" data_types_reverse = { TypeCode.BOOL: "BooleanField", TypeCode.BYTES: "BinaryField", @@ -26,16 +26,17 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): } def get_field_type(self, data_type, description): - """Define the type of the field. + """A hook for a Spanner database to use the cursor description to + match a Django field type to the database column. - :type data_type: str - :param data_type: The type name. + :type data_type: int + :param data_type: One of Spanner's standard data types. - :type description: str - :param description: The description of the type. + :type description: :class:`~google.cloud.spanner_dbapi._helpers.ColumnInfo` + :param description: A description of Spanner column data type. - :rtype: Any - :returns: Field type. + :rtype: str + :returns: The corresponding type of Django field. """ if data_type == TypeCode.STRING and description.internal_size == "MAX": return "TextField" @@ -45,27 +46,27 @@ def get_table_list(self, cursor): """Return a list of table and view names in the current database. :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` - :param cursor: The cursor in the linked database. + :param cursor: A reference to a Spanner Database cursor. :rtype: list - :returns: A list of table and view names. + :returns: A list of table and view names in the current database. """ # The second TableInfo field is 't' for table or 'v' for view. return [TableInfo(row[0], "t") for row in cursor.list_tables()] def get_table_description(self, cursor, table_name): - """ - Return a description of the table with the DB-API cursor.description + """Return a description of the table with the DB-API cursor.description interface. :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` - :param cursor: The cursor in the linked database. + :param cursor: A reference to a Spanner Database cursor. :type table_name: str :param table_name: The name of the table. :rtype: list - :returns: A description of the table. + :returns: A description of the table with the DB-API + cursor.description interface. """ cursor.execute( "SELECT * FROM %s LIMIT 1" @@ -99,22 +100,22 @@ def get_table_description(self, cursor, table_name): return descriptions def get_relations(self, cursor, table_name): - # TODO: PLEASE DO NOT USE THIS METHOD UNTIL - # https://github.com/orijtech/django-spanner/issues/313 - # is resolved so that foreign keys can be supported, as documented in: - # https://github.com/orijtech/django-spanner/issues/311 - """ - Return a dictionary of {field_name: (field_name_other_table, other_table)} - representing all relationships in the table. + """Return a dictionary of {field_name: (field_name_other_table, other_table)} + representing all the relationships in the table. + + TODO: DO NOT USE THIS METHOD UNTIL + https://github.com/orijtech/django-spanner/issues/313 + is resolved so that foreign keys can be supported, as documented in: + https://github.com/orijtech/django-spanner/issues/311 :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` - :param cursor: The cursor in the linked database. + :param cursor: A reference to a Spanner Database cursor. :type table_name: str - :param table_name: The name of the table. + :param table_name: The name of the Cloud Spanner Database. :rtype: dict - :returns: A dictionary of the field descriptions. + :returns: A dictionary representing column relationships to other tables. """ results = cursor.run_sql_in_snapshot( ''' @@ -143,7 +144,7 @@ def get_primary_key_column(self, cursor, table_name): """Return Primary Key column. :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` - :param cursor: The cursor in the linked database. + :param cursor: A reference to a Spanner Database cursor. :type table_name: str :param table_name: The name of the table. @@ -169,7 +170,7 @@ def get_primary_key_column(self, cursor, table_name): return results[0][0] if results else None def get_constraints(self, cursor, table_name): - """Return a dictionary the constraints in the current table. + """Retrieve the Spanner Table column constraints. :type cursor: :class:`~google.cloud.spanner_dbapi.cursor.Cursor` :param cursor: The cursor in the linked database. From 5580621729ccbfbedc313dd86edba45e487649fe Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 13:21:21 -0500 Subject: [PATCH 21/31] docs: updates to `lookups.py` --- django_spanner/lookups.py | 92 +++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/django_spanner/lookups.py b/django_spanner/lookups.py index 56e415eb0d..cad536c914 100644 --- a/django_spanner/lookups.py +++ b/django_spanner/lookups.py @@ -24,9 +24,21 @@ def contains(self, compiler, connection): - """Contains and icontains. + """A method to extend Django Contains and IContains classes. - TODO: describe all parameters when code will be ready. + :type self: :class:`~django.db.models.lookups.Contains` or + :class:`~django.db.models.lookups.IContains` + :param self: the instance of the class that owns this method. + + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. + + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. :rtype: tuple[str, str] :returns: A tuple of the SQL request and parameters. @@ -58,11 +70,22 @@ def contains(self, compiler, connection): def iexact(self, compiler, connection): - """ - Case-insensitive exact match. If the value provided for comparison is - None, it will be interpreted as an SQL NULL. + """A method to extend Django IExact class. Case-insensitive exact match. + If the value provided for comparison is None, it will be interpreted as + an SQL NULL. - TODO: describe all parameters when code will be ready. + :type self: :class:`~django.db.models.lookups.IExact` + :param self: the instance of the class that owns this method. + + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. + + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. :rtype: tuple[str, str] :returns: A tuple of the SQL request and parameters. @@ -84,9 +107,21 @@ def iexact(self, compiler, connection): def regex(self, compiler, connection): - """Regex and iregex. + """A method to extend Django Regex and IRegex classes. + + :type self: :class:`~django.db.models.lookups.Regex` or + :class:`~django.db.models.lookups.IRegex` + :param self: the instance of the class that owns this method. - TODO: describe all parameters when code will be ready. + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. + + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. :rtype: tuple[str, str] :returns: A tuple of the SQL request and parameters. @@ -112,9 +147,24 @@ def regex(self, compiler, connection): def startswith_endswith(self, compiler, connection): - """Startswith, endswith, istartswith, and iendswith lookups. + """A method to extend Django StartsWith, IStartsWith, EndsWith, and + IEndsWith classes. + + :type self: :class:`~django.db.models.lookups.StartsWith` or + :class:`~django.db.models.lookups.IStartsWith` or + :class:`~django.db.models.lookups.EndsWith` or + :class:`~django.db.models.lookups.IEndsWith` + :param self: the instance of the class that owns this method. + + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - TODO: describe all parameters when code will be ready. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. :rtype: tuple[str, str] :returns: A tuple of the SQL request and parameters. @@ -158,9 +208,25 @@ def startswith_endswith(self, compiler, connection): def cast_param_to_float(self, compiler, connection): - """Cast parameters of the expression into the float type. + """A method to extend Django Exact, GreaterThan, GreaterThanOrEqual, + LessThan, and LessThanOrEqual classes. + + :type self: :class:`~django.db.models.lookups.Exact` or + :class:`~django.db.models.lookups.GreaterThan` or + :class:`~django.db.models.lookups.GreaterThanOrEqual` or + :class:`~django.db.models.lookups.LessThan` or + :class:`~django.db.models.lookups.LessThanOrEqual` + :param self: the instance of the class that owns this method. + + :type compiler: :class:`~django_spanner.compiler.SQLCompilerst` + :param compiler: The query compiler responsible for generating the query. + Must have a compile method, returning a (sql, [params]) + tuple. Calling compiler(value) will return a quoted + `value`. - TODO: describe all parameters when code will be ready. + :type connection: :class:`~google.cloud.spanner_dbapi.connection.Connection` + :param connection: The Spanner database connection used for the current + query. :rtype: tuple[str, str] :returns: A tuple of the SQL request and float parameters. @@ -185,7 +251,7 @@ def cast_param_to_float(self, compiler, connection): def register_lookups(): - """Register a new lookup in the class.""" + """Registers the above methods with the corersponding Django classes.""" Contains.as_spanner = contains IContains.as_spanner = contains IExact.as_spanner = iexact From 7deb3d30cb799e968f45bc47eae7ba700b6fad40 Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Thu, 3 Dec 2020 13:33:27 -0500 Subject: [PATCH 22/31] docs: updates to `operations.py` --- django_spanner/operations.py | 76 +++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/django_spanner/operations.py b/django_spanner/operations.py index 906e23841d..095c3b592c 100644 --- a/django_spanner/operations.py +++ b/django_spanner/operations.py @@ -24,10 +24,11 @@ class DatabaseOperations(BaseDatabaseOperations): - """This class describes database operations.""" + """A Spanner-specific version of Django database operations.""" cast_data_types = {"CharField": "STRING", "TextField": "STRING"} cast_char_field_without_max_length = "STRING" compiler_module = "django_spanner.compiler" + # Django's lookup names that require a different name in Spanner's # EXTRACT() function. # https://cloud.google.com/spanner/docs/functions-and-operators#extract @@ -38,16 +39,30 @@ class DatabaseOperations(BaseDatabaseOperations): } def max_name_length(self): - """The quota for the maximum table name length. + """Get the maximum length of Spanner table and column names. + + See also: https://cloud.google.com/spanner/quotas#tables + + TODO: Consider changing the hardcoded output to a linked value. :rtype: int :returns: Maximum length of the name of the table. """ - # https://cloud.google.com/spanner/quotas#tables return 128 def quote_name(self, name): - """Change quote name to the necessary format. + """Returns a quoted version of the given table or column name. Also, + applies backticks to the name that either contain '-' or ' ', or is a + Cloud Spanner's reserved keyword. + + Spanner says "column name not valid" if spaces or hyphens are present + (although according to the documantation, any character should be + allowed in quoted identifiers). Replace problematic characters when + running the Django tests to prevent crashes. (Don't modify names in + normal operation to prevent the possibility of colliding with another + column.) + + See: https://github.com/googleapis/python-spanner-django/issues/204 :type name: str :param name: The Quota name. @@ -55,57 +70,63 @@ def quote_name(self, name): :rtype: :class:`str` :returns: Name escaped if it has to be escaped. """ - # Spanner says "column name not valid" if spaces or hyphens are present - # (although according the docs, any character should be allowed in - # quoted identifiers). Replace problematic characters when running the - # Django tests to prevent crashes. (Don't modify names in normal - # operation to prevent the possibility of colliding with another - # column.) https://github.com/orijtech/spanner-orm/issues/204 if os.environ.get("RUNNING_SPANNER_BACKEND_TESTS") == "1": name = name.replace(" ", "_").replace("-", "_") return escape_name(name) def bulk_batch_size(self, fields, objs): - """Return the maximum number of the query parameters. + """Overrides the base class method. Returns the maximum number of the + query parameters. - TODO: describe the rest of the parameters or remove them. + :type fields: list + :param fields: Currently not used. + + :type objs: list + :param objs: Currently not used. :rtype: int - :returns: The maximum number of the query parameters (constant). + :returns: The maximum number of query parameters (constant). """ return self.connection.features.max_query_params def bulk_insert_sql(self, fields, placeholder_rows): - """Bulk insert for SQLs. + """A helper method that stitches multiple values into a single SQL + record. - TODO: describe the rest of the parameters or remove them. + :type fields: list + :param fields: Currently not used. :type placeholder_rows: list - :param placeholder_rows: A list of placeholder rows. + :param placeholder_rows: Data "rows" containing values to combine. :rtype: str - :returns: A SQL statement. + :returns: A SQL statement (a `VALUES` command). """ placeholder_rows_sql = (", ".join(row) for row in placeholder_rows) values_sql = ", ".join("(%s)" % sql for sql in placeholder_rows_sql) return "VALUES " + values_sql - def sql_flush(self, style, tables, sequences, allow_cascade=False): - """ - Return a list of SQL statements required to remove all data from the - given database tables (without actually removing the tables themselves). + def sql_flush(self, style, tables, reset_sequences=False, allow_cascade=False): + """Overrides the base class method. Returns a list of SQL statements + required to remove all data from the given database tables (without + actually removing the tables themselves). :type style: :class:`~django.core.management.color.Style` - :param style: An object as returned by either color_style() or - no_style(). + :param style: (Currently not used) An object as returned by either + color_style() or no_style(). :type tables: list - :param tables: A list of tables names. + :param tables: A collection of Cloud Spanner Tables - TODO: describe the rest of the parameters or remove them. + :type reset_sequences: bool + :param reset_sequences: (Optional) Currently not used. + + :type allow_cascade: bool + :param allow_cascade: (Optional) Currently not used. :rtype: list - :returns: A list of SQL requests to tables. + :returns: A list of SQL statements required to remove all data from + the given database tables. """ # Cloud Spanner doesn't support TRUNCATE so DELETE instead. # A dummy WHERE clause is required. @@ -162,8 +183,7 @@ def adapt_datetimefield_value(self, value): def adapt_decimalfield_value( self, value, max_digits=None, decimal_places=None ): - """ - Convert value from decimal.Decimal into float, for a direct mapping + """Convert value from decimal.Decimal into float, for a direct mapping and correct serialization with RPCs to Cloud Spanner. :type value: #TODO From 67106fa6d435b72a95c411de72484ae3a92b9cd5 Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Mon, 7 Dec 2020 17:48:53 -0500 Subject: [PATCH 23/31] fix: docstrings --- django_spanner/operations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/django_spanner/operations.py b/django_spanner/operations.py index 095c3b592c..917954eb2a 100644 --- a/django_spanner/operations.py +++ b/django_spanner/operations.py @@ -143,9 +143,9 @@ def sql_flush(self, style, tables, reset_sequences=False, allow_cascade=False): return [] def adapt_datefield_value(self, value): - """Reformat date argument into Cloud Spanner. + """Cast date argument into Spanner DB API DateStr format. - :type value: #TODO + :type value: object :param value: A date argument. :rtype: :class:`~google.cloud.spanner_dbapi.types.DateStr` @@ -158,7 +158,7 @@ def adapt_datefield_value(self, value): def adapt_datetimefield_value(self, value): """Reformat time argument into Cloud Spanner. - :type value: #TODO + :type value: object :param value: A time argument. :rtype: :class:`~google.cloud.spanner_dbapi.types.TimestampStr` @@ -186,7 +186,7 @@ def adapt_decimalfield_value( """Convert value from decimal.Decimal into float, for a direct mapping and correct serialization with RPCs to Cloud Spanner. - :type value: #TODO + :type value: :class:`~google.cloud.spanner_v1.types.Numeric` :param value: A decimal field value. :type max_digits: int From c9364d00051af41a3ab71c5b89a4678855f0b0bc Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Mon, 7 Dec 2020 19:30:31 -0500 Subject: [PATCH 24/31] fix: typo --- django_spanner/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_spanner/base.py b/django_spanner/base.py index e7c4c62033..37872c9533 100644 --- a/django_spanner/base.py +++ b/django_spanner/base.py @@ -120,7 +120,7 @@ def get_connection_params(self): """Retrieve the connection parameters. :rtype: dict - :returns: A dictionary containing the Spanner connection parametersб + :returns: A dictionary containing the Spanner connection parameters in Django Spanner format. """ return { From 678ecb315aa6bb667983a07d6abc0361d24b8d21 Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Tue, 8 Dec 2020 20:06:10 -0500 Subject: [PATCH 25/31] fix: docstrings --- django_spanner/client.py | 15 ++++++++++----- django_spanner/expressions.py | 7 +++---- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/django_spanner/client.py b/django_spanner/client.py index e1d3de857c..81e45f078a 100644 --- a/django_spanner/client.py +++ b/django_spanner/client.py @@ -9,11 +9,16 @@ class DatabaseClient(BaseDatabaseClient): - """Wraps the Django base class. + """Wraps the Django base class.""" - TODO: Consider actual implementation of `runshell` method. - - :raises: :class:`~google.cloud.spanner_dbapi.exceptions.NotSupportedError` - """ def runshell(self, parameters): + """Overrides the base class method. + + TODO: Consider actual implementation of this method. + + :type parameters: list + :param parameters: Currently not used. + + :raises: :class:`~google.cloud.spanner_dbapi.exceptions.NotSupportedError` + """ raise NotSupportedError("This method is not supported.") diff --git a/django_spanner/expressions.py b/django_spanner/expressions.py index f04f3d4eff..e7033b3b62 100644 --- a/django_spanner/expressions.py +++ b/django_spanner/expressions.py @@ -11,13 +11,12 @@ def order_by(self, compiler, connection, **extra_context): """Order expressions in the SQL query and generate a new query using Spanner-specific templates. - TODO: In Django 3.1, this can be replaced with - DatabaseFeatures.supports_order_by_nulls_modifier = False. - Also, consider making this function a part of a class. - :rtype: str :returns: A SQL query. """ + # TODO: In Django 3.1, this can be replaced with + # DatabaseFeatures.supports_order_by_nulls_modifier = False. + # Also, consider making this a class method. template = None if self.nulls_last: template = "%(expression)s IS NULL, %(expression)s %(ordering)s" From 0b84fb6195377b491bc444342b92b77909f28a5d Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Wed, 9 Dec 2020 14:16:15 +0300 Subject: [PATCH 26/31] fix: single quotes --- django_spanner/schema.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/django_spanner/schema.py b/django_spanner/schema.py index 11dd92079c..b6c859c466 100644 --- a/django_spanner/schema.py +++ b/django_spanner/schema.py @@ -131,7 +131,7 @@ def create_model(self, model): def delete_model(self, model): """ - Drop the model’s table in the database along with any unique constraints + Drop the model's table in the database along with any unique constraints or indexes it has. :type model: :class:`~django.db.migrations.operations.models.ModelOperation` @@ -148,7 +148,7 @@ def delete_model(self, model): def add_field(self, model, field): """ - Add a column (or sometimes multiple) to the model’s table to + Add a column (or sometimes multiple) to the model's table to represent the field. This will also add indexes or a unique constraint if the field has db_index=True or unique=True. If the field is a ManyToManyField without a value for through, instead of creating a @@ -233,7 +233,7 @@ def add_field(self, model, field): def remove_field(self, model, field): """ - Remove the column(s) representing the field from the model’s table, + Remove the column(s) representing the field from the model's table, along with any unique constraints, foreign key constraints, or indexes caused by that field. If the field is a ManyToManyField without a value for through, it will remove the table created to track the @@ -304,7 +304,7 @@ def column_sql( return sql, params def add_index(self, model, index): - """Add index to model’s table. + """Add index to model's table. :type model: :class:`~django.db.migrations.operations.models.ModelOperation` :param model: A model for creating a table. From 583398dd03f837d542c68588c7b24d529edf209b Mon Sep 17 00:00:00 2001 From: Tina Smirnova Date: Wed, 9 Dec 2020 14:27:53 +0300 Subject: [PATCH 27/31] fix: docstrings typing --- django_spanner/base.py | 2 +- django_spanner/client.py | 4 ++-- django_spanner/compiler.py | 5 +++-- django_spanner/creation.py | 10 ++++++---- django_spanner/expressions.py | 3 ++- django_spanner/functions.py | 37 +++++++++++++++++++++++------------ django_spanner/operations.py | 17 ++++++++++------ 7 files changed, 49 insertions(+), 29 deletions(-) diff --git a/django_spanner/base.py b/django_spanner/base.py index 37872c9533..b6620b7dfb 100644 --- a/django_spanner/base.py +++ b/django_spanner/base.py @@ -132,7 +132,7 @@ def get_connection_params(self): } def get_new_connection(self, **conn_params): - """Creates a new connection with corresponding connection parameters. + """Create a new connection with corresponding connection parameters. :type conn_params: list :param conn_params: A List of the connection parameters for diff --git a/django_spanner/client.py b/django_spanner/client.py index 81e45f078a..2aa7600d02 100644 --- a/django_spanner/client.py +++ b/django_spanner/client.py @@ -9,10 +9,10 @@ class DatabaseClient(BaseDatabaseClient): - """Wraps the Django base class.""" + """Wrap the Django base class.""" def runshell(self, parameters): - """Overrides the base class method. + """Override the base class method. TODO: Consider actual implementation of this method. diff --git a/django_spanner/compiler.py b/django_spanner/compiler.py index f08647a528..4cd7dfb877 100644 --- a/django_spanner/compiler.py +++ b/django_spanner/compiler.py @@ -17,12 +17,13 @@ class SQLCompiler(BaseSQLCompiler): - """A variation of the Django SQL compiler, adjusted for Spanner-specific + """ + A variation of the Django SQL compiler, adjusted for Spanner-specific functionality. """ def get_combinator_sql(self, combinator, all): - """Overrides the native Django method. + """Override the native Django method. Copied from the base class except for: combinator_sql += ' ALL' if all else ' DISTINCT' diff --git a/django_spanner/creation.py b/django_spanner/creation.py index 4e02f39cf6..ee73decc0a 100644 --- a/django_spanner/creation.py +++ b/django_spanner/creation.py @@ -14,7 +14,8 @@ class DatabaseCreation(BaseDatabaseCreation): - """Spanner-specific wrapper for Django class encapsulating methods for + """ + Spanner-specific wrapper for Django class encapsulating methods for creation and destruction of the underlying test database. """ @@ -34,7 +35,7 @@ def mark_skips(self): ) def create_test_db(self, *args, **kwargs): - """Creates a test database. + """Create a test database. :rtype: str :returns: The name of the newly created test Database. @@ -46,8 +47,9 @@ def create_test_db(self, *args, **kwargs): super().create_test_db(*args, **kwargs) def _create_test_db(self, verbosity, autoclobber, keepdb=False): - """Creates dummy test tables. This method is mostly copied from the - base class but removes usage of _nodb_connection since Spanner doesn't + """ + Create dummy test tables. This method is mostly copied from the + base class but removes usage of `_nodb_connection` since Spanner doesn't have or need one. """ # Mostly copied from the base class but removes usage of diff --git a/django_spanner/expressions.py b/django_spanner/expressions.py index e7033b3b62..526b6a7362 100644 --- a/django_spanner/expressions.py +++ b/django_spanner/expressions.py @@ -8,7 +8,8 @@ def order_by(self, compiler, connection, **extra_context): - """Order expressions in the SQL query and generate a new query using + """ + Order expressions in the SQL query and generate a new query using Spanner-specific templates. :rtype: str diff --git a/django_spanner/functions.py b/django_spanner/functions.py index 9379ef6a64..bc02d0b5d8 100644 --- a/django_spanner/functions.py +++ b/django_spanner/functions.py @@ -27,13 +27,14 @@ class IfNull(Func): - """Represents SQL `IFNULL` function.""" + """Represent SQL `IFNULL` function.""" function = "IFNULL" arity = 2 def cast(self, compiler, connection, **extra_context): - """A method to extend Django Cast class. Cast SQL query for given + """ + A method to extend Django Cast class. Cast SQL query for given parameters. :type self: :class:`~django.db.models.functions.comparison.Cast` @@ -65,7 +66,8 @@ def cast(self, compiler, connection, **extra_context): def chr_(self, compiler, connection, **extra_context): - """A method to extend Django Chr class. Returns a SQL query where the code + """ + A method to extend Django Chr class. Returns a SQL query where the code points are displayed as a string. :type self: :class:`~django.db.models.functions.text.Chr` @@ -94,7 +96,8 @@ def chr_(self, compiler, connection, **extra_context): def concatpair(self, compiler, connection, **extra_context): - """A method to extend Django ConcatPair class. Concatenates a SQL query + """ + A method to extend Django ConcatPair class. Concatenates a SQL query into the sequence of :class:`IfNull` objects. :type self: :class:`~django.db.models.functions.text.ConcatPair` @@ -124,7 +127,8 @@ def concatpair(self, compiler, connection, **extra_context): def cot(self, compiler, connection, **extra_context): - """A method to extend Django Cot class. Returns a SQL query of calculated + """ + A method to extend Django Cot class. Returns a SQL query of calculated trigonometric cotangent function. :type self: :class:`~django.db.models.functions.math.Cot` @@ -153,7 +157,8 @@ def cot(self, compiler, connection, **extra_context): def degrees(self, compiler, connection, **extra_context): - """A method to extend Django Degress class. Returns a SQL query of the + """ + A method to extend Django Degress class. Returns a SQL query of the angle converted to degrees. :type self: :class:`~django.db.models.functions.math.Degrees` @@ -206,7 +211,8 @@ def left_and_right(self, compiler, connection, **extra_context): def log(self, compiler, connection, **extra_context): - """A method to extend Django Log class. Returns a SQL query of calculated + """ + A method to extend Django Log class. Returns a SQL query of calculated logarithm. :type self: :class:`~django.db.models.functions.math.Log` @@ -234,7 +240,8 @@ def log(self, compiler, connection, **extra_context): def ord_(self, compiler, connection, **extra_context): - """A method to extend Django Ord class. Returns a SQL query of the + """ + A method to extend Django Ord class. Returns a SQL query of the expression converted to ord. :type self: :class:`~django.db.models.functions.text.Ord` @@ -263,7 +270,8 @@ def ord_(self, compiler, connection, **extra_context): def pi(self, compiler, connection, **extra_context): - """A method to extend Django Pi class. Returns a SQL query of the Pi + """ + A method to extend Django Pi class. Returns a SQL query of the Pi constant. :type self: :class:`~django.db.models.functions.math.Pi` @@ -289,7 +297,8 @@ def pi(self, compiler, connection, **extra_context): def radians(self, compiler, connection, **extra_context): - """A method to extend Django Radians class. Returns a SQL query of the + """ + A method to extend Django Radians class. Returns a SQL query of the angle converted to radians. :type self: :class:`~django.db.models.functions.math.Radians` @@ -318,7 +327,8 @@ def radians(self, compiler, connection, **extra_context): def strindex(self, compiler, connection, **extra_context): - """A method to extend Django StrIndex class. Returns a SQL query of the + """ + A method to extend Django StrIndex class. Returns a SQL query of the string position. :type self: :class:`~django.db.models.functions.text.StrIndex` @@ -344,7 +354,8 @@ def strindex(self, compiler, connection, **extra_context): def substr(self, compiler, connection, **extra_context): - """A method to extend Django Substr class. Returns a SQL query of a + """ + A method to extend Django Substr class. Returns a SQL query of a substring. :type self: :class:`~django.db.models.functions.text.Substr` @@ -370,7 +381,7 @@ def substr(self, compiler, connection, **extra_context): def register_functions(): - """Registers the above methods with the corersponding Django classes.""" + """Register the above methods with the corersponding Django classes.""" Cast.as_spanner = cast Chr.as_spanner = chr_ ConcatPair.as_spanner = concatpair diff --git a/django_spanner/operations.py b/django_spanner/operations.py index 917954eb2a..3495958723 100644 --- a/django_spanner/operations.py +++ b/django_spanner/operations.py @@ -51,7 +51,8 @@ def max_name_length(self): return 128 def quote_name(self, name): - """Returns a quoted version of the given table or column name. Also, + """ + Return a quoted version of the given table or column name. Also, applies backticks to the name that either contain '-' or ' ', or is a Cloud Spanner's reserved keyword. @@ -75,7 +76,8 @@ def quote_name(self, name): return escape_name(name) def bulk_batch_size(self, fields, objs): - """Overrides the base class method. Returns the maximum number of the + """ + Override the base class method. Returns the maximum number of the query parameters. :type fields: list @@ -90,7 +92,8 @@ def bulk_batch_size(self, fields, objs): return self.connection.features.max_query_params def bulk_insert_sql(self, fields, placeholder_rows): - """A helper method that stitches multiple values into a single SQL + """ + A helper method that stitches multiple values into a single SQL record. :type fields: list @@ -107,7 +110,8 @@ def bulk_insert_sql(self, fields, placeholder_rows): return "VALUES " + values_sql def sql_flush(self, style, tables, reset_sequences=False, allow_cascade=False): - """Overrides the base class method. Returns a list of SQL statements + """ + Override the base class method. Returns a list of SQL statements required to remove all data from the given database tables (without actually removing the tables themselves). @@ -183,7 +187,8 @@ def adapt_datetimefield_value(self, value): def adapt_decimalfield_value( self, value, max_digits=None, decimal_places=None ): - """Convert value from decimal.Decimal into float, for a direct mapping + """ + Convert value from decimal.Decimal into float, for a direct mapping and correct serialization with RPCs to Cloud Spanner. :type value: :class:`~google.cloud.spanner_v1.types.Numeric` @@ -205,7 +210,7 @@ def adapt_decimalfield_value( def adapt_timefield_value(self, value): """ - Transforms a time value to an object compatible with what is expected + Transform a time value to an object compatible with what is expected by the backend driver for time columns. :type value: #TODO From 2afc4eb9dc9703dcdf90267a78abdbf935d96408 Mon Sep 17 00:00:00 2001 From: Chris Kleinknecht Date: Thu, 10 Dec 2020 14:17:45 -0800 Subject: [PATCH 28/31] Remove runshell docstring --- django_spanner/client.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/django_spanner/client.py b/django_spanner/client.py index 2aa7600d02..3dcb326310 100644 --- a/django_spanner/client.py +++ b/django_spanner/client.py @@ -12,13 +12,4 @@ class DatabaseClient(BaseDatabaseClient): """Wrap the Django base class.""" def runshell(self, parameters): - """Override the base class method. - - TODO: Consider actual implementation of this method. - - :type parameters: list - :param parameters: Currently not used. - - :raises: :class:`~google.cloud.spanner_dbapi.exceptions.NotSupportedError` - """ raise NotSupportedError("This method is not supported.") From d36b9abd628dc2fd4a2d4535583d8dfc4ada3b73 Mon Sep 17 00:00:00 2001 From: Chris Kleinknecht Date: Thu, 10 Dec 2020 14:18:00 -0800 Subject: [PATCH 29/31] Remove some TODOs from docstrings --- django_spanner/operations.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/django_spanner/operations.py b/django_spanner/operations.py index 3495958723..f3b207e4bd 100644 --- a/django_spanner/operations.py +++ b/django_spanner/operations.py @@ -38,13 +38,12 @@ class DatabaseOperations(BaseDatabaseOperations): "week_day": "dayofweek", } + # TODO: Consider changing the hardcoded output to a linked value. def max_name_length(self): """Get the maximum length of Spanner table and column names. See also: https://cloud.google.com/spanner/quotas#tables - TODO: Consider changing the hardcoded output to a linked value. - :rtype: int :returns: Maximum length of the name of the table. """ @@ -252,13 +251,14 @@ def get_db_converters(self, expression): converters.append(self.convert_uuidfield_value) return converters + # TODO: here and below, remove unused expression and connection parameters + # or include them to the code. def convert_binaryfield_value(self, value, expression, connection): """Convert a binary field to Cloud Spanner. :type value: #TODO :param value: A binary field value. - #TODO remove unused expression and connection parameters or include them to the code. :rtype: b64decode :returns: A base64 encoded bytes. @@ -274,8 +274,6 @@ def convert_datetimefield_value(self, value, expression, connection): :type value: #TODO :param value: A binary field value. - #TODO remove unused expression and connection parameters or include them to the code. - :rtype: datetime :returns: A DateTime in the format for Cloud Spanner. """ @@ -307,8 +305,6 @@ def convert_decimalfield_value(self, value, expression, connection): :type value: #TODO :param value: A decimal field. - #TODO remove unused expression and connection parameters or include them to the code. - :rtype: :class:`Decimal` :returns: A decimal field in the Cloud Spanner format. """ @@ -323,8 +319,6 @@ def convert_timefield_value(self, value, expression, connection): :type value: #TODO :param value: A time field. - #TODO remove unused expression and connection parameters or include them to the code. - :rtype: :class:`time` :returns: A time field in the Cloud Spanner format. """ @@ -339,8 +333,6 @@ def convert_uuidfield_value(self, value, expression, connection): :type value: #TODO :param value: A UUID field. - #TODO remove unused expression and connection parameters or include them to the code. - :rtype: :class:`UUID` :returns: A UUID field in the Cloud Spanner format. """ From 5240d2183268ead4662f75a6b4e5b57e0d927f0c Mon Sep 17 00:00:00 2001 From: Chris Kleinknecht Date: Tue, 15 Dec 2020 12:38:06 -0800 Subject: [PATCH 30/31] Fix operations docstrings --- django_spanner/operations.py | 49 +++++++++++++++++------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/django_spanner/operations.py b/django_spanner/operations.py index f3b207e4bd..1041554ab3 100644 --- a/django_spanner/operations.py +++ b/django_spanner/operations.py @@ -212,7 +212,7 @@ def adapt_timefield_value(self, value): Transform a time value to an object compatible with what is expected by the backend driver for time columns. - :type value: #TODO + :type value: `datetime.datetime` :param value: A time field value. :rtype: :class:`~google.cloud.spanner_dbapi.types.TimestampStr` @@ -231,11 +231,11 @@ def adapt_timefield_value(self, value): def get_db_converters(self, expression): """Get a list of functions needed to convert field data. - :type expression: #TODO finish class in functions.py + :type expression: `django.db.models.expressions.Expression` :param expression: An expression to convert. :rtype: list - :returns: A list of functions. + :returns: Converter functions to apply to Spanner field values. """ converters = super().get_db_converters(expression) internal_type = expression.output_field.get_internal_type() @@ -251,16 +251,13 @@ def get_db_converters(self, expression): converters.append(self.convert_uuidfield_value) return converters - # TODO: here and below, remove unused expression and connection parameters - # or include them to the code. def convert_binaryfield_value(self, value, expression, connection): - """Convert a binary field to Cloud Spanner. + """Convert Spanner BinaryField value for Django. - :type value: #TODO - :param value: A binary field value. + :type value: bytes + :param value: A base64-encoded binary field value. - - :rtype: b64decode + :rtype: bytes :returns: A base64 encoded bytes. """ if value is None: @@ -269,13 +266,13 @@ def convert_binaryfield_value(self, value, expression, connection): return b64decode(value) def convert_datetimefield_value(self, value, expression, connection): - """Convert a date and time field to Cloud Spanner. + """Convert Spanner DateTimeField value for Django. - :type value: #TODO - :param value: A binary field value. + :type value: `DatetimeWithNanoseconds` + :param value: A datetime field value. :rtype: datetime - :returns: A DateTime in the format for Cloud Spanner. + :returns: A TZ-aware datetime. """ if value is None: return value @@ -300,13 +297,13 @@ def convert_datetimefield_value(self, value, expression, connection): ) def convert_decimalfield_value(self, value, expression, connection): - """Convert a decimal field to Cloud Spanner. + """Convert Spanner DecimalField value for Django. - :type value: #TODO + :type value: float :param value: A decimal field. :rtype: :class:`Decimal` - :returns: A decimal field in the Cloud Spanner format. + :returns: A converted decimal field. """ if value is None: return value @@ -314,13 +311,13 @@ def convert_decimalfield_value(self, value, expression, connection): return Decimal(str(value)) def convert_timefield_value(self, value, expression, connection): - """Convert a time field to Cloud Spanner. + """Convert Spanner TimeField value for Django. - :type value: #TODO - :param value: A time field. + :type value: `DatetimeWithNanoseconds` + :param value: A datetime/time field. - :rtype: :class:`time` - :returns: A time field in the Cloud Spanner format. + :rtype: :class:`datetime.time` + :returns: A converted datetime. """ if value is None: return value @@ -330,11 +327,11 @@ def convert_timefield_value(self, value, expression, connection): def convert_uuidfield_value(self, value, expression, connection): """Convert a UUID field to Cloud Spanner. - :type value: #TODO - :param value: A UUID field. + :type value: str + :param value: A UUID-valued str. - :rtype: :class:`UUID` - :returns: A UUID field in the Cloud Spanner format. + :rtype: :class:`uuid.UUID` + :returns: A converted UUID. """ if value is not None: value = UUID(value) From d7493564c8767159e37b16183de6fc8ddce98e0b Mon Sep 17 00:00:00 2001 From: "STATION\\MF" Date: Wed, 16 Dec 2020 00:08:24 -0500 Subject: [PATCH 31/31] docs: docstrings --- django_spanner/operations.py | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/django_spanner/operations.py b/django_spanner/operations.py index 1041554ab3..6ce0260c81 100644 --- a/django_spanner/operations.py +++ b/django_spanner/operations.py @@ -231,8 +231,8 @@ def adapt_timefield_value(self, value): def get_db_converters(self, expression): """Get a list of functions needed to convert field data. - :type expression: `django.db.models.expressions.Expression` - :param expression: An expression to convert. + :type expression: :class:`django.db.models.expressions.BaseExpression` + :param expression: A query expression to convert. :rtype: list :returns: Converter functions to apply to Spanner field values. @@ -257,6 +257,12 @@ def convert_binaryfield_value(self, value, expression, connection): :type value: bytes :param value: A base64-encoded binary field value. + :type expression: :class:`django.db.models.expressions.BaseExpression` + :param expression: A query expression. + + :type connection: :class:`~google.cloud.cpanner_dbapi.connection.Connection` + :param connection: Reference to a Spanner database connection. + :rtype: bytes :returns: A base64 encoded bytes. """ @@ -271,6 +277,12 @@ def convert_datetimefield_value(self, value, expression, connection): :type value: `DatetimeWithNanoseconds` :param value: A datetime field value. + :type expression: :class:`django.db.models.expressions.BaseExpression` + :param expression: A query expression. + + :type connection: :class:`~google.cloud.cpanner_dbapi.connection.Connection` + :param connection: Reference to a Spanner database connection. + :rtype: datetime :returns: A TZ-aware datetime. """ @@ -302,6 +314,12 @@ def convert_decimalfield_value(self, value, expression, connection): :type value: float :param value: A decimal field. + :type expression: :class:`django.db.models.expressions.BaseExpression` + :param expression: A query expression. + + :type connection: :class:`~google.cloud.cpanner_dbapi.connection.Connection` + :param connection: Reference to a Spanner database connection. + :rtype: :class:`Decimal` :returns: A converted decimal field. """ @@ -313,9 +331,15 @@ def convert_decimalfield_value(self, value, expression, connection): def convert_timefield_value(self, value, expression, connection): """Convert Spanner TimeField value for Django. - :type value: `DatetimeWithNanoseconds` + :type value: :class:`~google.api_core.datetime_helpers.DatetimeWithNanoseconds` :param value: A datetime/time field. + :type expression: :class:`django.db.models.expressions.BaseExpression` + :param expression: A query expression. + + :type connection: :class:`~google.cloud.cpanner_dbapi.connection.Connection` + :param connection: Reference to a Spanner database connection. + :rtype: :class:`datetime.time` :returns: A converted datetime. """ @@ -330,6 +354,12 @@ def convert_uuidfield_value(self, value, expression, connection): :type value: str :param value: A UUID-valued str. + :type expression: :class:`django.db.models.expressions.BaseExpression` + :param expression: A query expression. + + :type connection: :class:`~google.cloud.cpanner_dbapi.connection.Connection` + :param connection: Reference to a Spanner database connection. + :rtype: :class:`uuid.UUID` :returns: A converted UUID. """