databricks-sql-connector 4.1.1__tar.gz → 4.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/CHANGELOG.md +8 -0
  2. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/PKG-INFO +4 -2
  3. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/pyproject.toml +1 -1
  4. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/__init__.py +1 -1
  5. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/utils/constants.py +1 -0
  6. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/thrift_backend.py +42 -13
  7. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/client.py +68 -14
  8. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/utils.py +3 -3
  9. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/LICENSE +0 -0
  10. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/README.md +0 -0
  11. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/__init__.py +0 -0
  12. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/__init__.py +0 -0
  13. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/auth.py +0 -0
  14. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/authenticators.py +0 -0
  15. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/common.py +0 -0
  16. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/endpoint.py +0 -0
  17. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/oauth.py +0 -0
  18. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/oauth_http_handler.py +0 -0
  19. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/retry.py +0 -0
  20. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/auth/thrift_http_client.py +0 -0
  21. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/databricks_client.py +0 -0
  22. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/backend.py +0 -0
  23. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/models/__init__.py +0 -0
  24. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/models/base.py +0 -0
  25. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/models/requests.py +0 -0
  26. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/models/responses.py +0 -0
  27. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/queue.py +0 -0
  28. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/result_set.py +0 -0
  29. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/utils/conversion.py +0 -0
  30. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/utils/filters.py +0 -0
  31. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/utils/http_client.py +0 -0
  32. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/sea/utils/normalize.py +0 -0
  33. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/types.py +0 -0
  34. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/utils/__init__.py +0 -0
  35. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/backend/utils/guid_utils.py +0 -0
  36. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/cloudfetch/download_manager.py +0 -0
  37. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/cloudfetch/downloader.py +0 -0
  38. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/common/feature_flag.py +0 -0
  39. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/common/http.py +0 -0
  40. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/common/http_utils.py +0 -0
  41. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/common/unified_http_client.py +0 -0
  42. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/exc.py +0 -0
  43. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/experimental/__init__.py +0 -0
  44. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/experimental/oauth_persistence.py +0 -0
  45. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/parameters/__init__.py +0 -0
  46. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/parameters/native.py +0 -0
  47. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/parameters/py.typed +0 -0
  48. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/py.typed +0 -0
  49. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/result_set.py +0 -0
  50. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/session.py +0 -0
  51. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/telemetry/latency_logger.py +0 -0
  52. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/telemetry/models/endpoint_models.py +0 -0
  53. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/telemetry/models/enums.py +0 -0
  54. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/telemetry/models/event.py +0 -0
  55. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/telemetry/models/frontend_logs.py +0 -0
  56. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/telemetry/telemetry_client.py +0 -0
  57. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/telemetry/utils.py +0 -0
  58. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/thrift_api/TCLIService/TCLIService-remote +0 -0
  59. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/thrift_api/TCLIService/TCLIService.py +0 -0
  60. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/thrift_api/TCLIService/__init__.py +0 -0
  61. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/thrift_api/TCLIService/constants.py +0 -0
  62. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/thrift_api/TCLIService/ttypes.py +0 -0
  63. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/thrift_api/__init__.py +0 -0
  64. {databricks_sql_connector-4.1.1 → databricks_sql_connector-4.1.3}/src/databricks/sql/types.py +0 -0
@@ -1,5 +1,13 @@
1
1
  # Release History
2
2
 
3
+ # 4.1.3 (2025-09-17)
4
+ - Query tags integration (databricks/databricks-sql-python#663 by @sreekanth-db)
5
+ - Add variant support (databricks/databricks-sql-python#560 by @shivam2680)
6
+
7
+ # 4.1.2 (2025-08-22)
8
+ - Streaming ingestion support for PUT operation (databricks/databricks-sql-python#643 by @sreekanth-db)
9
+ - Removed use_threads argument on concat_tables for compatibility with pyarrow<14 (databricks/databricks-sql-python#684 by @jprakash-db)
10
+
3
11
  # 4.1.1 (2025-08-21)
4
12
  - Add documentation for proxy support (databricks/databricks-sql-python#680 by @vikrantpuppala)
5
13
  - Fix compatibility with urllib3<2 and add CI actions to improve dependency checks (databricks/databricks-sql-python#678 by @vikrantpuppala)
@@ -1,8 +1,9 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: databricks-sql-connector
3
- Version: 4.1.1
3
+ Version: 4.1.3
4
4
  Summary: Databricks SQL Connector for Python
5
5
  License: Apache-2.0
6
+ License-File: LICENSE
6
7
  Author: Databricks
7
8
  Author-email: databricks-sql-connector-maintainers@databricks.com
8
9
  Requires-Python: >=3.8.0,<4.0.0
@@ -14,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
14
15
  Classifier: Programming Language :: Python :: 3.11
15
16
  Classifier: Programming Language :: Python :: 3.12
16
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
17
19
  Provides-Extra: pyarrow
18
20
  Requires-Dist: lz4 (>=4.0.2,<5.0.0)
19
21
  Requires-Dist: oauthlib (>=3.1.0,<4.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "databricks-sql-connector"
3
- version = "4.1.1"
3
+ version = "4.1.3"
4
4
  description = "Databricks SQL Connector for Python"
5
5
  authors = ["Databricks <databricks-sql-connector-maintainers@databricks.com>"]
6
6
  license = "Apache-2.0"
@@ -68,7 +68,7 @@ DATETIME = DBAPITypeObject("timestamp")
68
68
  DATE = DBAPITypeObject("date")
69
69
  ROWID = DBAPITypeObject()
70
70
 
71
- __version__ = "4.1.1"
71
+ __version__ = "4.1.3"
72
72
  USER_AGENT_NAME = "PyDatabricksSqlConnector"
73
73
 
74
74
  # These two functions are pyhive legacy
@@ -15,6 +15,7 @@ ALLOWED_SESSION_CONF_TO_DEFAULT_VALUES_MAP: Dict[str, str] = {
15
15
  "STATEMENT_TIMEOUT": "0",
16
16
  "TIMEZONE": "UTC",
17
17
  "USE_CACHED_RESULT": "true",
18
+ "QUERY_TAGS": "",
18
19
  }
19
20
 
20
21
 
@@ -735,7 +735,7 @@ class ThriftDatabricksClient(DatabricksClient):
735
735
  return pyarrow.schema([convert_col(col) for col in t_table_schema.columns])
736
736
 
737
737
  @staticmethod
738
- def _col_to_description(col, session_id_hex=None):
738
+ def _col_to_description(col, field=None, session_id_hex=None):
739
739
  type_entry = col.typeDesc.types[0]
740
740
 
741
741
  if type_entry.primitiveEntry:
@@ -764,12 +764,39 @@ class ThriftDatabricksClient(DatabricksClient):
764
764
  else:
765
765
  precision, scale = None, None
766
766
 
767
+ # Extract variant type from field if available
768
+ if field is not None:
769
+ try:
770
+ # Check for variant type in metadata
771
+ if field.metadata and b"Spark:DataType:SqlName" in field.metadata:
772
+ sql_type = field.metadata.get(b"Spark:DataType:SqlName")
773
+ if sql_type == b"VARIANT":
774
+ cleaned_type = "variant"
775
+ except Exception as e:
776
+ logger.debug(f"Could not extract variant type from field: {e}")
777
+
767
778
  return col.columnName, cleaned_type, None, None, precision, scale, None
768
779
 
769
780
  @staticmethod
770
- def _hive_schema_to_description(t_table_schema, session_id_hex=None):
781
+ def _hive_schema_to_description(
782
+ t_table_schema, schema_bytes=None, session_id_hex=None
783
+ ):
784
+ field_dict = {}
785
+ if pyarrow and schema_bytes:
786
+ try:
787
+ arrow_schema = pyarrow.ipc.read_schema(pyarrow.py_buffer(schema_bytes))
788
+ # Build a dictionary mapping column names to fields
789
+ for field in arrow_schema:
790
+ field_dict[field.name] = field
791
+ except Exception as e:
792
+ logger.debug(f"Could not parse arrow schema: {e}")
793
+
771
794
  return [
772
- ThriftDatabricksClient._col_to_description(col, session_id_hex)
795
+ ThriftDatabricksClient._col_to_description(
796
+ col,
797
+ field_dict.get(col.columnName) if field_dict else None,
798
+ session_id_hex,
799
+ )
773
800
  for col in t_table_schema.columns
774
801
  ]
775
802
 
@@ -802,11 +829,6 @@ class ThriftDatabricksClient(DatabricksClient):
802
829
  or direct_results.resultSet.hasMoreRows
803
830
  )
804
831
 
805
- description = self._hive_schema_to_description(
806
- t_result_set_metadata_resp.schema,
807
- self._session_id_hex,
808
- )
809
-
810
832
  if pyarrow:
811
833
  schema_bytes = (
812
834
  t_result_set_metadata_resp.arrowSchema
@@ -819,6 +841,12 @@ class ThriftDatabricksClient(DatabricksClient):
819
841
  else:
820
842
  schema_bytes = None
821
843
 
844
+ description = self._hive_schema_to_description(
845
+ t_result_set_metadata_resp.schema,
846
+ schema_bytes,
847
+ self._session_id_hex,
848
+ )
849
+
822
850
  lz4_compressed = t_result_set_metadata_resp.lz4Compressed
823
851
  command_id = CommandId.from_thrift_handle(resp.operationHandle)
824
852
 
@@ -863,11 +891,6 @@ class ThriftDatabricksClient(DatabricksClient):
863
891
 
864
892
  t_result_set_metadata_resp = resp.resultSetMetadata
865
893
 
866
- description = self._hive_schema_to_description(
867
- t_result_set_metadata_resp.schema,
868
- self._session_id_hex,
869
- )
870
-
871
894
  if pyarrow:
872
895
  schema_bytes = (
873
896
  t_result_set_metadata_resp.arrowSchema
@@ -880,6 +903,12 @@ class ThriftDatabricksClient(DatabricksClient):
880
903
  else:
881
904
  schema_bytes = None
882
905
 
906
+ description = self._hive_schema_to_description(
907
+ t_result_set_metadata_resp.schema,
908
+ schema_bytes,
909
+ self._session_id_hex,
910
+ )
911
+
883
912
  lz4_compressed = t_result_set_metadata_resp.lz4Compressed
884
913
  is_staging_operation = t_result_set_metadata_resp.isStagingOperation
885
914
  has_more_rows = resp.hasMoreRows
@@ -1,5 +1,5 @@
1
1
  import time
2
- from typing import Dict, Tuple, List, Optional, Any, Union, Sequence
2
+ from typing import Dict, Tuple, List, Optional, Any, Union, Sequence, BinaryIO
3
3
  import pandas
4
4
 
5
5
  try:
@@ -662,7 +662,9 @@ class Cursor:
662
662
  )
663
663
 
664
664
  def _handle_staging_operation(
665
- self, staging_allowed_local_path: Union[None, str, List[str]]
665
+ self,
666
+ staging_allowed_local_path: Union[None, str, List[str]],
667
+ input_stream: Optional[BinaryIO] = None,
666
668
  ):
667
669
  """Fetch the HTTP request instruction from a staging ingestion command
668
670
  and call the designated handler.
@@ -671,6 +673,28 @@ class Cursor:
671
673
  is not descended from staging_allowed_local_path.
672
674
  """
673
675
 
676
+ assert self.active_result_set is not None
677
+ row = self.active_result_set.fetchone()
678
+ assert row is not None
679
+
680
+ # May be real headers, or could be json string
681
+ headers = (
682
+ json.loads(row.headers) if isinstance(row.headers, str) else row.headers
683
+ )
684
+ headers = dict(headers) if headers else {}
685
+
686
+ # Handle __input_stream__ token for PUT operations
687
+ if (
688
+ row.operation == "PUT"
689
+ and getattr(row, "localFile", None) == "__input_stream__"
690
+ ):
691
+ return self._handle_staging_put_stream(
692
+ presigned_url=row.presignedUrl,
693
+ stream=input_stream,
694
+ headers=headers,
695
+ )
696
+
697
+ # For non-streaming operations, validate staging_allowed_local_path
674
698
  if isinstance(staging_allowed_local_path, type(str())):
675
699
  _staging_allowed_local_paths = [staging_allowed_local_path]
676
700
  elif isinstance(staging_allowed_local_path, type(list())):
@@ -685,10 +709,6 @@ class Cursor:
685
709
  os.path.abspath(i) for i in _staging_allowed_local_paths
686
710
  ]
687
711
 
688
- assert self.active_result_set is not None
689
- row = self.active_result_set.fetchone()
690
- assert row is not None
691
-
692
712
  # Must set to None in cases where server response does not include localFile
693
713
  abs_localFile = None
694
714
 
@@ -711,19 +731,16 @@ class Cursor:
711
731
  session_id_hex=self.connection.get_session_id_hex(),
712
732
  )
713
733
 
714
- # May be real headers, or could be json string
715
- headers = (
716
- json.loads(row.headers) if isinstance(row.headers, str) else row.headers
717
- )
718
-
719
734
  handler_args = {
720
735
  "presigned_url": row.presignedUrl,
721
736
  "local_file": abs_localFile,
722
- "headers": dict(headers) or {},
737
+ "headers": headers,
723
738
  }
724
739
 
725
740
  logger.debug(
726
- f"Attempting staging operation indicated by server: {row.operation} - {getattr(row, 'localFile', '')}"
741
+ "Attempting staging operation indicated by server: %s - %s",
742
+ row.operation,
743
+ getattr(row, "localFile", ""),
727
744
  )
728
745
 
729
746
  # TODO: Create a retry loop here to re-attempt if the request times out or fails
@@ -762,6 +779,10 @@ class Cursor:
762
779
  HttpMethod.PUT, presigned_url, body=fh.read(), headers=headers
763
780
  )
764
781
 
782
+ self._handle_staging_http_response(r)
783
+
784
+ def _handle_staging_http_response(self, r):
785
+
765
786
  # fmt: off
766
787
  # HTTP status codes
767
788
  OK = 200
@@ -784,6 +805,37 @@ class Cursor:
784
805
  + "but not yet applied on the server. It's possible this command may fail later."
785
806
  )
786
807
 
808
+ @log_latency(StatementType.SQL)
809
+ def _handle_staging_put_stream(
810
+ self,
811
+ presigned_url: str,
812
+ stream: BinaryIO,
813
+ headers: dict = {},
814
+ ) -> None:
815
+ """Handle PUT operation with streaming data.
816
+
817
+ Args:
818
+ presigned_url: The presigned URL for upload
819
+ stream: Binary stream to upload
820
+ headers: HTTP headers
821
+
822
+ Raises:
823
+ ProgrammingError: If no input stream is provided
824
+ OperationalError: If the upload fails
825
+ """
826
+
827
+ if not stream:
828
+ raise ProgrammingError(
829
+ "No input stream provided for streaming operation",
830
+ session_id_hex=self.connection.get_session_id_hex(),
831
+ )
832
+
833
+ r = self.connection.http_client.request(
834
+ HttpMethod.PUT, presigned_url, body=stream.read(), headers=headers
835
+ )
836
+
837
+ self._handle_staging_http_response(r)
838
+
787
839
  @log_latency(StatementType.SQL)
788
840
  def _handle_staging_get(
789
841
  self, local_file: str, presigned_url: str, headers: Optional[dict] = None
@@ -840,6 +892,7 @@ class Cursor:
840
892
  operation: str,
841
893
  parameters: Optional[TParameterCollection] = None,
842
894
  enforce_embedded_schema_correctness=False,
895
+ input_stream: Optional[BinaryIO] = None,
843
896
  ) -> "Cursor":
844
897
  """
845
898
  Execute a query and wait for execution to complete.
@@ -914,7 +967,8 @@ class Cursor:
914
967
 
915
968
  if self.active_result_set and self.active_result_set.is_staging_operation:
916
969
  self._handle_staging_operation(
917
- staging_allowed_local_path=self.connection.staging_allowed_local_path
970
+ staging_allowed_local_path=self.connection.staging_allowed_local_path,
971
+ input_stream=input_stream,
918
972
  )
919
973
 
920
974
  return self
@@ -298,7 +298,7 @@ class CloudFetchQueue(ResultSetQueue, ABC):
298
298
  num_rows -= table_slice.num_rows
299
299
 
300
300
  logger.debug("CloudFetchQueue: collected {} next rows".format(results.num_rows))
301
- return pyarrow.concat_tables(partial_result_chunks, use_threads=True)
301
+ return concat_table_chunks(partial_result_chunks)
302
302
 
303
303
  def remaining_rows(self) -> "pyarrow.Table":
304
304
  """
@@ -321,7 +321,7 @@ class CloudFetchQueue(ResultSetQueue, ABC):
321
321
  self.table_row_index += table_slice.num_rows
322
322
  self.table = self._create_next_table()
323
323
  self.table_row_index = 0
324
- return pyarrow.concat_tables(partial_result_chunks, use_threads=True)
324
+ return concat_table_chunks(partial_result_chunks)
325
325
 
326
326
  def _create_table_at_offset(self, offset: int) -> Union["pyarrow.Table", None]:
327
327
  """Create next table at the given row offset"""
@@ -880,7 +880,7 @@ def concat_table_chunks(
880
880
  result_table[j].extend(table_chunks[i].column_table[j])
881
881
  return ColumnTable(result_table, table_chunks[0].column_names)
882
882
  else:
883
- return pyarrow.concat_tables(table_chunks, use_threads=True)
883
+ return pyarrow.concat_tables(table_chunks)
884
884
 
885
885
 
886
886
  def build_client_context(server_hostname: str, version: str, **kwargs):