diff --git a/src/snowflake/snowpark/dataframe.py b/src/snowflake/snowpark/dataframe.py index 4afde3b5f1..e70420c8c4 100644 --- a/src/snowflake/snowpark/dataframe.py +++ b/src/snowflake/snowpark/dataframe.py @@ -6200,14 +6200,33 @@ def sample( n: Optional[int] = None, _emit_ast: bool = True, ) -> "DataFrame": - """Samples rows based on either the number of rows to be returned or a - percentage of rows to be returned. + """Returns a random sample of rows using Snowflake's SQL SAMPLE clause. + + Specify either ``frac`` or ``n``. Fractional sampling includes each row + with the given probability, so the number of returned rows can vary. + Fixed-size sampling returns the requested number of rows, or all rows + if the input contains fewer rows. Neither form guarantees row order. + Repeated executions can return different samples; this method has no + seed parameter. For seeded sampling of a table, see :meth:`Table.sample`. + + See `SAMPLE `_ + for SQL sampling semantics. Args: - frac: the percentage of rows to be sampled. + frac: The probability of selecting each row, from 0.0 to 1.0 + inclusive. For example, 0.1 requests approximately 10 percent + of the rows, not exactly 10 percent. n: the number of rows to sample in the range of 0 to 1,000,000 (inclusive). + Returns: a :class:`DataFrame` containing the sample of rows. + + Examples:: + + >>> df = session.range(100) + >>> fractional_sample = df.sample(frac=0.1) + >>> fixed_sample = df.sample(n=5) + >>> assert fixed_sample.count() == 5 """ DataFrame._validate_sample_input(frac, n) @@ -6263,6 +6282,15 @@ def ai(self) -> DataFrameAIFunctions: """ Returns a :class:`DataFrameAIFunctions` object that provides AI-powered functions for the DataFrame. + + Access this namespace through an existing DataFrame, for example + ``df.ai``. It is not a column and accessing it does not itself execute + an AI function. Call a method on the namespace to build an AI operation. + + See :meth:`DataFrameAIFunctions.classify`, + :meth:`DataFrameAIFunctions.extract`, and + :meth:`DataFrameAIFunctions.sentiment` for parameters and examples. + The :class:`DataFrameAIFunctions` reference lists the available methods. """ return self._ai diff --git a/src/snowflake/snowpark/dataframe_reader.py b/src/snowflake/snowpark/dataframe_reader.py index 17cbc60e7e..fd618e149c 100644 --- a/src/snowflake/snowpark/dataframe_reader.py +++ b/src/snowflake/snowpark/dataframe_reader.py @@ -1026,6 +1026,26 @@ def load(self, path: Optional[str] = None, _emit_ast: bool = True) -> DataFrame: def csv(self, path: str, _emit_ast: bool = True) -> DataFrame: """Specify the path of the CSV file(s) to load. + Configure the reader with :meth:`option`, :meth:`options`, and + :meth:`schema` before calling ``csv``. Options are not keyword + arguments of this method. Common CSV format options include + ``FIELD_DELIMITER``, ``SKIP_HEADER``, and + ``FIELD_OPTIONALLY_ENCLOSED_BY``. See the + `CSV format options + `_ + for their accepted values. + + Provide a schema explicitly or enable the reader's ``INFER_SCHEMA`` + option. Schema inference can issue queries while constructing the + DataFrame and is not supported for CSV in local testing mode. + + Example (requires an existing stage containing a header row and two + comma-separated columns):: + + >>> from snowflake.snowpark.types import StructType, StructField, IntegerType, StringType + >>> schema = StructType([StructField("id", IntegerType()), StructField("name", StringType())]) + >>> df = session.read.schema(schema).option("SKIP_HEADER", 1).csv("@my_stage/people.csv") # doctest: +SKIP + Args: path: The stage location of a CSV file, or a stage location that has CSV files. diff --git a/src/snowflake/snowpark/dataframe_stat_functions.py b/src/snowflake/snowpark/dataframe_stat_functions.py index 306025f52d..b5b17dc350 100644 --- a/src/snowflake/snowpark/dataframe_stat_functions.py +++ b/src/snowflake/snowpark/dataframe_stat_functions.py @@ -74,7 +74,9 @@ def approx_quantile( Args: col: The name of the numeric column. - percentile: A list of float values greater than or equal to 0.0 and less than 1.0. + percentile: A list of float values between 0.0 and 1.0, inclusive. + For example, 0.5 requests the approximate median and 1.0 requests + the approximate maximum. statement_params: Dictionary of statement level parameters to be set while executing this action. Returns: diff --git a/src/snowflake/snowpark/exceptions.py b/src/snowflake/snowpark/exceptions.py index 5ae5ace724..d3bbcb09bf 100644 --- a/src/snowflake/snowpark/exceptions.py +++ b/src/snowflake/snowpark/exceptions.py @@ -77,6 +77,23 @@ class SnowparkSQLException(SnowparkClientException): Includes all error codes in range 13XX (where XX is 0-9). This exception is specifically raised for error codes: 1300, 1304. + + Attributes: + error_code: Snowpark client error code. This is distinct from the + underlying Snowflake SQL error code. + sql_error_code: Snowflake SQL error number, when provided by the connector. + sfqid: Snowflake query ID, when available. Use this to locate the failed + statement in `Query History + `_. + query: SQL text associated with the error, when available. + raw_message: Underlying error message, when available. + conn_error: Original connector exception, when available. + + Read the error message first: a compilation error, missing object, and + insufficient privilege require different fixes even if they share a + Snowpark error code. Check the failed query's database, schema, role, and + referenced identifiers. When requesting help, include the query ID and + error codes; redact sensitive literals from SQL and error messages. """ def __init__( diff --git a/src/snowflake/snowpark/file_operation.py b/src/snowflake/snowpark/file_operation.py index 2a0f4fbefc..4415a11664 100644 --- a/src/snowflake/snowpark/file_operation.py +++ b/src/snowflake/snowpark/file_operation.py @@ -213,6 +213,12 @@ def get( The command lists all files in the specified path and applies the regular expression pattern on each of the files found. Default: ``None`` (all files in the specified stage are downloaded). statement_params: Dictionary of statement level parameters to be set while executing this action. + For example, ``{"QUERY_TAG": "download_files"}`` tags the GET + statement. See `Snowflake parameters + `_ for + parameter meanings and allowed values. Only parameters supported + at statement level apply; this is not a way to set account-only + parameters or GET options such as ``parallel`` and ``pattern``. Returns: A ``list`` of :class:`GetResult` instances, each of which represents the result of a downloaded file. diff --git a/src/snowflake/snowpark/files.py b/src/snowflake/snowpark/files.py index 3be46b45a8..622329b0ba 100644 --- a/src/snowflake/snowpark/files.py +++ b/src/snowflake/snowpark/files.py @@ -156,7 +156,19 @@ def open( require_scoped_url: bool = True, ) -> SnowflakeFile: """ - Used to create a :class:`~snowflake.snowpark.file.SnowflakeFile` which can only be used for read-based IO operations on the file. + Opens a file for reading and returns a :class:`SnowflakeFile` stream. + Use a ``with`` statement to close the stream after reading. + + For example, inside a handler with a caller-provided scoped URL:: + + from snowflake.snowpark.files import SnowflakeFile + + def read_text(url): + with SnowflakeFile.open(url, "r") as source: + return source.read() + + This example reads the whole file into memory. Use binary mode + (``"rb"``) when the consumer expects bytes rather than text. In UDFs and Stored Procedures, the object works like a read-only Python IOBase object and as a wrapper for an IO stream of remote files. diff --git a/src/snowflake/snowpark/functions.py b/src/snowflake/snowpark/functions.py index 9f35db90f9..ca0b02c2e4 100644 --- a/src/snowflake/snowpark/functions.py +++ b/src/snowflake/snowpark/functions.py @@ -7356,7 +7356,10 @@ def array_insert( def array_position( variant: ColumnOrName, array: ColumnOrName, _emit_ast: bool = True ) -> Column: - """Returns the index of the first occurrence of an element in an ARRAY. + """Returns the zero-based index of the first occurrence of an element in an ARRAY. + + The first element has index 0. If the value is not present, returns SQL NULL + (represented by ``None`` in a collected Row), not -1. Args: variant: Column containing the VARIANT value that you want to find. The function @@ -7364,16 +7367,16 @@ def array_position( array: Column containing the ARRAY to be searched. Example:: - >>> from snowflake.snowpark import Row - >>> df = session.create_dataframe([Row([2, 1]), Row([1, 3])], schema=["a"]) - >>> df.select(array_position(lit(1), "a").alias("result")).show() - ------------ - |"RESULT" | - ------------ - |1 | - |0 | - ------------ - + >>> from snowflake.snowpark.functions import array_position, lit + >>> df = session.create_dataframe( + ... [(1, [2, 1, 1]), (2, [1, 3]), (3, [4, 5])], + ... schema=["id", "values"]) + >>> df.select("id", array_position(lit(1), "values").alias("position")).sort("id").collect() + [Row(ID=1, POSITION=1), Row(ID=2, POSITION=0), Row(ID=3, POSITION=None)] + + In the first row, 1 appears twice; the result is the position of its first + occurrence. Use :func:`lit` to search for a literal value rather than a + column name. """ v = _to_col_if_str(variant, "array_position") a = _to_col_if_str(array, "array_position") @@ -8834,13 +8837,20 @@ def iff( expr1: A :class:`Column` expression or a literal value, which will be returned if ``condition`` is true. expr2: A :class:`Column` expression or a literal value, which will be returned - if ``condition`` is false. + if ``condition`` is false or NULL. Examples:: - >>> df = session.create_dataframe([True, False, None], schema=["a"]) - >>> df.select(iff(df["a"], lit("true"), lit("false")).alias("iff")).collect() - [Row(IFF='true'), Row(IFF='false'), Row(IFF='false')] + >>> from snowflake.snowpark.functions import iff, lit + >>> df = session.create_dataframe( + ... [(1, True), (2, False), (3, None)], schema=["id", "approved"]) + >>> df.select( + ... "id", iff(df["approved"], lit("ship"), lit("hold")).alias("action") + ... ).sort("id").collect() + [Row(ID=1, ACTION='ship'), Row(ID=2, ACTION='hold'), Row(ID=3, ACTION='hold')] + + Only an approved row selects ``"ship"``. Both false and unknown (NULL) + approval select ``"hold"``. """ ast = build_function_expr("iff", [condition, expr1, expr2]) if _emit_ast else None return _call_function( diff --git a/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py b/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py index 049411b846..1f3674a48b 100644 --- a/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py +++ b/src/snowflake/snowpark/modin/plugin/extensions/pd_extensions.py @@ -513,6 +513,41 @@ def to_snowflake( index_label: Optional[IndexLabel] = None, table_type: Literal["", "temp", "temporary", "transient"] = "", ) -> None: + """Save a Snowpark pandas DataFrame or Series as a Snowflake table. + + Args: + obj: The Snowpark pandas DataFrame or Series to write. + name: Destination table name or fully-qualified identifier, such as + ``"MY_DB.MY_SCHEMA.MY_TABLE"`` or + ``["MY_DB", "MY_SCHEMA", "MY_TABLE"]``. Unqualified names use the + session's current database and schema. Double-quote identifier + components to preserve mixed case. + if_exists: How to handle an existing table. Defaults to ``"fail"``: + + - ``"fail"``: Raise ValueError if the table exists. + - ``"replace"``: Drop the table and write the new values. + - ``"append"``: Add rows to the existing table. + + index: If True (the default), save index levels as table columns in + addition to the data columns. Set False to omit the index. + index_label: Column label or labels for the saved index. Defaults to + the index names. Supply a label for an unnamed index and one label + per level for a MultiIndex. Labels must not duplicate data-column + labels. Ignored when ``index=False``. + table_type: Type of table to create: ``"temp"`` or ``"temporary"``, + ``"transient"``, or ``""`` (the default) for a permanent table. + See `table types + `_. + + Returns: + None. The write is executed by this call. Row ordering is not preserved + by a Snowflake table. + + See also: + :func:`DataFrame.to_snowflake `, + :func:`Series.to_snowflake `, + :func:`read_snowflake `. + """ _snowpark_pandas_obj_check(obj) return obj.to_snowflake( name=name, diff --git a/src/snowflake/snowpark/session.py b/src/snowflake/snowpark/session.py index bb83a230d8..13b84be1d8 100644 --- a/src/snowflake/snowpark/session.py +++ b/src/snowflake/snowpark/session.py @@ -1062,6 +1062,23 @@ def conf(self) -> RuntimeConfig: def sql_simplifier_enabled(self) -> bool: """Set to ``True`` to use the SQL simplifier (defaults to ``True``). The generated SQLs from ``DataFrame`` transformations would have fewer layers of nested queries if the SQL simplifier is enabled. + + Set this property before constructing the DataFrame whose SQL you want + to inspect. SQL text can change between library versions; compare + :attr:`DataFrame.queries` rather than relying on a specific SQL string. + + Example:: + + >>> original_setting = session.sql_simplifier_enabled + >>> try: + ... session.sql_simplifier_enabled = True + ... df = session.range(10).select("id").filter("id > 2") + ... simplified_queries = df.queries["queries"] + ... session.sql_simplifier_enabled = False + ... df = session.range(10).select("id").filter("id > 2") + ... unsimplified_queries = df.queries["queries"] + ... finally: + ... session.sql_simplifier_enabled = original_setting """ return self._sql_simplifier_enabled @@ -3156,6 +3173,19 @@ def sql( or :func:`DataFrame.to_pandas` evaluate the DataFrame. For **immediate execution**, chain the call with the collect method: `session.sql(query).collect()`. + SQL compilation and execution errors usually surface when an action + executes the query, not when this method creates the DataFrame. + Operations that request schema metadata can also contact Snowflake + before collection. Catch :class:`~snowflake.snowpark.exceptions.SnowparkSQLException` + around the operation that triggers evaluation; inspect its message + and query ID to diagnose the server error. This is not an exhaustive + list of possible client, connection, or argument errors. + + Raises: + NotImplementedError: SQL execution is not supported in local testing + mode. See `mocking SQL operations + `_. + Args: query: The SQL statement to execute. params: binding parameters. We only support qmark bind variables. For more information, check @@ -3520,7 +3550,11 @@ def write_pandas( Args: df: The pandas DataFrame or Snowpark pandas DataFrame or Series we'd like to write back. - table_name: Name of the table we want to insert into. + table_name: Name of the table we want to insert into, without the + database or schema prefix. Pass those separately through + ``database`` and ``schema``. For example, use + ``table_name="MY_TABLE", database="MY_DB", schema="MY_SCHEMA"``, + not ``table_name="MY_DB.MY_SCHEMA.MY_TABLE"``. database: Database that the table is in. If not provided, the default one will be used. schema: Schema that the table is in. If not provided, the default one will be used. chunk_size: Number of rows to be inserted once. If not provided, all rows will be dumped once. @@ -3537,6 +3571,10 @@ def write_pandas( quote_identifiers: By default, identifiers, specifically database, schema, table and column names (from :attr:`DataFrame.columns`) will be quoted. If set to ``False``, identifiers are passed on to Snowflake without quoting, i.e. identifiers will be coerced to uppercase by Snowflake. + With the default ``True``, names must match the stored case: + an object created with an unquoted name normally has an uppercase + name, whereas a quoted mixed-case name must retain its case. + Do not uppercase names of quoted mixed-case objects. auto_create_table: When true, automatically creates a table to store the passed in pandas DataFrame using the passed in ``database``, ``schema``, and ``table_name``. Note: there are usually multiple table configurations that would allow you to upload a particular pandas DataFrame successfully. If you don't like the auto created